Compare commits

...

24 Commits

Author SHA1 Message Date
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
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
Erik Montnemery
4f8e7125d4 Add state based condition tests (#168349) 2026-04-16 11:22:14 +02:00
renovate[bot]
baf5e32c59 Update xmltodict to 1.0.4 (#168330)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 10:49:35 +02:00
renovate[bot]
0f0ceaace2 Update PyJWT to 2.12.1 (#168239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Robert Resch <robert@resch.dev>
2026-04-16 10:44:41 +02:00
Andres Ruiz
5ecae7066b Bump waterfurance to 1.6.5 (#168328) 2026-04-16 10:09:25 +02:00
Ronald van der Meer
ac9bf9b7cb Bump python-duco-client to 0.3.1 (#168341) 2026-04-16 10:08:41 +02:00
renovate[bot]
d4a98c3336 Update audioop-lts to 0.2.2 (#168326)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 10:07:45 +02:00
dependabot[bot]
f0aae350b5 Bump docker/build-push-action from 7.0.0 to 7.1.0 (#168338)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-16 10:06:09 +02:00
Paulus Schoutsen
69332ed822 Add SerialSelector (#168263) 2026-04-16 10:45:37 +03:00
Erik Montnemery
32db17fab9 Add duration to more triggers (#168337) 2026-04-16 08:46:58 +02:00
renovate[bot]
84e8cff2ea Update infrared-protocols to 1.2.0 (#168335)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-16 08:31:56 +02:00
Ariel Ebersberger
cfe390e4f6 Migrate demo image_processing to async (#168315) 2026-04-16 08:17:00 +02:00
116 changed files with 1286 additions and 251 deletions

View File

@@ -530,7 +530,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -543,7 +543,7 @@ jobs:
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
id: push
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile

5
Dockerfile generated
View File

@@ -28,8 +28,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 +48,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

@@ -7,23 +7,31 @@ to speed up the process.
from __future__ import annotations
from collections.abc import Container, Iterable, Sequence
from datetime import timedelta
from functools import lru_cache, partial
from typing import Any
from functools import lru_cache
from typing import Any, override
from jwt import DecodeError, PyJWS, PyJWT
from jwt import DecodeError, PyJWK, PyJWS, PyJWT
from jwt.algorithms import AllowedPublicKeys
from jwt.types import Options
from homeassistant.util.json import json_loads
JWT_TOKEN_CACHE_SIZE = 16
MAX_TOKEN_SIZE = 8192
_VERIFY_KEYS = ("signature", "exp", "nbf", "iat", "aud", "iss", "sub", "jti")
_VERIFY_OPTIONS: dict[str, Any] = {f"verify_{key}": True for key in _VERIFY_KEYS} | {
"require": []
}
_NO_VERIFY_OPTIONS = {f"verify_{key}": False for key in _VERIFY_KEYS}
_NO_VERIFY_OPTIONS = Options(
verify_signature=False,
verify_exp=False,
verify_nbf=False,
verify_iat=False,
verify_aud=False,
verify_iss=False,
verify_sub=False,
verify_jti=False,
require=[],
)
class _PyJWSWithLoadCache(PyJWS):
@@ -38,9 +46,6 @@ class _PyJWSWithLoadCache(PyJWS):
return super()._load(jwt)
_jws = _PyJWSWithLoadCache()
@lru_cache(maxsize=JWT_TOKEN_CACHE_SIZE)
def _decode_payload(json_payload: str) -> dict[str, Any]:
"""Decode the payload from a JWS dictionary."""
@@ -56,21 +61,12 @@ def _decode_payload(json_payload: str) -> dict[str, Any]:
class _PyJWTWithVerify(PyJWT):
"""PyJWT with a fast decode implementation."""
def decode_payload(
self, jwt: str, key: str, options: dict[str, Any], algorithms: list[str]
) -> dict[str, Any]:
"""Decode a JWT's payload."""
if len(jwt) > MAX_TOKEN_SIZE:
# Avoid caching impossible tokens
raise DecodeError("Token too large")
return _decode_payload(
_jws.decode_complete(
jwt=jwt,
key=key,
algorithms=algorithms,
options=options,
)["payload"]
)
def __init__(self) -> None:
"""Initialize the PyJWT instance."""
# We require exp and iat claims to be present
super().__init__(Options(require=["exp", "iat"]))
# Override the _jws instance with our cached version
self._jws = _PyJWSWithLoadCache()
def verify_and_decode(
self,
@@ -79,37 +75,70 @@ class _PyJWTWithVerify(PyJWT):
algorithms: list[str],
issuer: str | None = None,
leeway: float | timedelta = 0,
options: dict[str, Any] | None = None,
options: Options | None = None,
) -> dict[str, Any]:
"""Verify a JWT's signature and claims."""
merged_options = {**_VERIFY_OPTIONS, **(options or {})}
payload = self.decode_payload(
return self.decode(
jwt=jwt,
key=key,
options=merged_options,
algorithms=algorithms,
)
# These should never be missing since we verify them
# but this is an additional safeguard to make sure
# nothing slips through.
assert "exp" in payload, "exp claim is required"
assert "iat" in payload, "iat claim is required"
self._validate_claims(
payload=payload,
options=merged_options,
issuer=issuer,
leeway=leeway,
options=options,
)
return payload
@override
def decode(
self,
jwt: str | bytes,
key: AllowedPublicKeys | PyJWK | str | bytes = "",
algorithms: Sequence[str] | None = None,
options: Options | None = None,
verify: bool | None = None,
detached_payload: bytes | None = None,
audience: str | Iterable[str] | None = None,
subject: str | None = None,
issuer: str | Container[str] | None = None,
leeway: float | timedelta = 0,
**kwargs: Any,
) -> dict[str, Any]:
"""Decode a JWT, verifying the signature and claims."""
if len(jwt) > MAX_TOKEN_SIZE:
# Avoid caching impossible tokens
raise DecodeError("Token too large")
return super().decode(
jwt=jwt,
key=key,
algorithms=algorithms,
options=options,
verify=verify,
detached_payload=detached_payload,
audience=audience,
subject=subject,
issuer=issuer,
leeway=leeway,
**kwargs,
)
@override
def _decode_payload(self, decoded: dict[str, Any]) -> dict[str, Any]:
return _decode_payload(decoded["payload"])
_jwt = _PyJWTWithVerify()
verify_and_decode = _jwt.verify_and_decode
unverified_hs256_token_decode = lru_cache(maxsize=JWT_TOKEN_CACHE_SIZE)(
partial(
_jwt.decode_payload, key="", algorithms=["HS256"], options=_NO_VERIFY_OPTIONS
@lru_cache(maxsize=JWT_TOKEN_CACHE_SIZE)
def unverified_hs256_token_decode(jwt: str) -> dict[str, Any]:
"""Decode a JWT without verifying the signature."""
return _jwt.decode(
jwt=jwt,
key="",
algorithms=["HS256"],
options=_NO_VERIFY_OPTIONS,
)
)
__all__ = [
"unverified_hs256_token_decode",

View File

@@ -3,6 +3,7 @@
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -249,6 +250,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -269,6 +273,9 @@
"fields": {
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
}
},
"name": "Carbon monoxide cleared"
@@ -279,6 +286,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -290,6 +300,9 @@
"fields": {
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
}
},
"name": "Carbon monoxide detected"
@@ -299,6 +312,9 @@
"fields": {
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
}
},
"name": "Gas cleared"
@@ -308,6 +324,9 @@
"fields": {
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
}
},
"name": "Gas detected"
@@ -327,6 +346,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -348,6 +370,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -369,6 +394,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -390,6 +418,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -411,6 +442,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -432,6 +466,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -453,6 +490,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -474,6 +514,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -485,6 +528,9 @@
"fields": {
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
}
},
"name": "Smoke cleared"
@@ -494,6 +540,9 @@
"fields": {
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
}
},
"name": "Smoke detected"
@@ -513,6 +562,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -534,6 +586,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}
@@ -555,6 +610,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::trigger_threshold_name%]"
}

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
# --- Unit lists for multi-unit pollutants ---
@@ -163,6 +168,7 @@
# Binary sensor detected/cleared trigger fields
.trigger_binary_fields: &trigger_binary_fields
behavior: *trigger_behavior
for: *trigger_for
# --- Binary sensor targets ---
@@ -294,6 +300,7 @@ co_crossed_threshold:
target: *target_co_sensor
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -320,6 +327,7 @@ co2_crossed_threshold:
target: *target_co2
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -344,6 +352,7 @@ pm1_crossed_threshold:
target: *target_pm1
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -368,6 +377,7 @@ pm25_crossed_threshold:
target: *target_pm25
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -392,6 +402,7 @@ pm4_crossed_threshold:
target: *target_pm4
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -416,6 +427,7 @@ pm10_crossed_threshold:
target: *target_pm10
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -442,6 +454,7 @@ ozone_crossed_threshold:
target: *target_ozone
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -470,6 +483,7 @@ voc_crossed_threshold:
target: *target_voc
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -498,6 +512,7 @@ voc_ratio_crossed_threshold:
target: *target_voc_ratio
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -526,6 +541,7 @@ no_crossed_threshold:
target: *target_no
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -554,6 +570,7 @@ no2_crossed_threshold:
target: *target_no2
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -580,6 +597,7 @@ n2o_crossed_threshold:
target: *target_n2o
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -606,6 +624,7 @@ so2_crossed_threshold:
target: *target_so2
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_armed": {
@@ -234,6 +235,9 @@
"fields": {
"behavior": {
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::alarm_control_panel::common::trigger_for_name%]"
}
},
"name": "Alarm armed"
@@ -243,6 +247,9 @@
"fields": {
"behavior": {
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::alarm_control_panel::common::trigger_for_name%]"
}
},
"name": "Alarm armed away"
@@ -252,6 +259,9 @@
"fields": {
"behavior": {
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::alarm_control_panel::common::trigger_for_name%]"
}
},
"name": "Alarm armed home"
@@ -261,6 +271,9 @@
"fields": {
"behavior": {
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::alarm_control_panel::common::trigger_for_name%]"
}
},
"name": "Alarm armed night"
@@ -270,6 +283,9 @@
"fields": {
"behavior": {
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::alarm_control_panel::common::trigger_for_name%]"
}
},
"name": "Alarm armed vacation"
@@ -279,6 +295,9 @@
"fields": {
"behavior": {
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::alarm_control_panel::common::trigger_for_name%]"
}
},
"name": "Alarm disarmed"
@@ -288,6 +307,9 @@
"fields": {
"behavior": {
"name": "[%key:component::alarm_control_panel::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::alarm_control_panel::common::trigger_for_name%]"
}
},
"name": "Alarm triggered"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
armed: *trigger_common

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_idle": {
@@ -160,6 +161,9 @@
"fields": {
"behavior": {
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::assist_satellite::common::trigger_for_name%]"
}
},
"name": "Satellite became idle"
@@ -169,6 +173,9 @@
"fields": {
"behavior": {
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::assist_satellite::common::trigger_for_name%]"
}
},
"name": "Satellite started listening"
@@ -178,6 +185,9 @@
"fields": {
"behavior": {
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::assist_satellite::common::trigger_for_name%]"
}
},
"name": "Satellite started processing"
@@ -187,6 +197,9 @@
"fields": {
"behavior": {
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::assist_satellite::common::trigger_for_name%]"
}
},
"name": "Satellite started responding"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
idle: *trigger_common
listening: *trigger_common

View File

@@ -3,6 +3,7 @@
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -87,6 +88,9 @@
"behavior": {
"name": "[%key:component::battery::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::battery::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::battery::common::trigger_threshold_name%]"
}
@@ -98,6 +102,9 @@
"fields": {
"behavior": {
"name": "[%key:component::battery::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::battery::common::trigger_for_name%]"
}
},
"name": "Battery low"
@@ -107,6 +114,9 @@
"fields": {
"behavior": {
"name": "[%key:component::battery::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::battery::common::trigger_for_name%]"
}
},
"name": "Battery not low"
@@ -116,6 +126,9 @@
"fields": {
"behavior": {
"name": "[%key:component::battery::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::battery::common::trigger_for_name%]"
}
},
"name": "Battery started charging"
@@ -125,6 +138,9 @@
"fields": {
"behavior": {
"name": "[%key:component::battery::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::battery::common::trigger_for_name%]"
}
},
"name": "Battery stopped charging"

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
.battery_threshold_entity: &battery_threshold_entity
- domain: input_number
@@ -42,21 +47,25 @@
low:
fields:
behavior: *trigger_behavior
for: *trigger_for
target: *trigger_target_battery
not_low:
fields:
behavior: *trigger_behavior
for: *trigger_for
target: *trigger_target_battery
started_charging:
fields:
behavior: *trigger_behavior
for: *trigger_for
target: *trigger_target_charging
stopped_charging:
fields:
behavior: *trigger_behavior
for: *trigger_for
target: *trigger_target_charging
level_changed:
@@ -74,6 +83,7 @@ level_crossed_threshold:
target: *trigger_target_percentage
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

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

@@ -3,6 +3,7 @@
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -385,6 +386,9 @@
"behavior": {
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::climate::common::trigger_for_name%]"
},
"hvac_mode": {
"description": "The HVAC modes to trigger on.",
"name": "Modes"
@@ -397,6 +401,9 @@
"fields": {
"behavior": {
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::climate::common::trigger_for_name%]"
}
},
"name": "Climate-control device started cooling"
@@ -406,6 +413,9 @@
"fields": {
"behavior": {
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::climate::common::trigger_for_name%]"
}
},
"name": "Climate-control device started drying"
@@ -415,6 +425,9 @@
"fields": {
"behavior": {
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::climate::common::trigger_for_name%]"
}
},
"name": "Climate-control device started heating"
@@ -434,6 +447,9 @@
"behavior": {
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::climate::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::climate::common::trigger_threshold_name%]"
}
@@ -455,6 +471,9 @@
"behavior": {
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::climate::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::climate::common::trigger_threshold_name%]"
}
@@ -466,6 +485,9 @@
"fields": {
"behavior": {
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::climate::common::trigger_for_name%]"
}
},
"name": "Climate-control device turned off"
@@ -475,6 +497,9 @@
"fields": {
"behavior": {
"name": "[%key:component::climate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::climate::common::trigger_for_name%]"
}
},
"name": "Climate-control device turned on"

View File

@@ -13,6 +13,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
.humidity_threshold_entity: &humidity_threshold_entity
- domain: input_number
@@ -50,6 +55,7 @@ hvac_mode_changed:
target: *trigger_climate_target
fields:
behavior: *trigger_behavior
for: *trigger_for
hvac_mode:
context:
filter_target: target
@@ -76,6 +82,7 @@ target_humidity_crossed_threshold:
target: *trigger_climate_target
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:
@@ -101,6 +108,7 @@ target_temperature_crossed_threshold:
target: *trigger_climate_target
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

View File

@@ -1,6 +1,7 @@
{
"common": {
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_value": {
@@ -96,6 +97,9 @@
"fields": {
"behavior": {
"name": "[%key:component::counter::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::counter::common::trigger_for_name%]"
}
},
"name": "Counter reached maximum"
@@ -105,6 +109,9 @@
"fields": {
"behavior": {
"name": "[%key:component::counter::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::counter::common::trigger_for_name%]"
}
},
"name": "Counter reached minimum"
@@ -114,6 +121,9 @@
"fields": {
"behavior": {
"name": "[%key:component::counter::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::counter::common::trigger_for_name%]"
}
},
"name": "Counter reset"

View File

@@ -13,6 +13,11 @@
- first
- last
- any
for:
required: true
default: 00:00:00
selector:
duration:
incremented:
target:

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"awning_is_closed": {
@@ -254,6 +255,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Awning closed"
@@ -263,6 +267,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Awning opened"
@@ -272,6 +279,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Blind closed"
@@ -281,6 +291,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Blind opened"
@@ -290,6 +303,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Curtain closed"
@@ -299,6 +315,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Curtain opened"
@@ -308,6 +327,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Shade closed"
@@ -317,6 +339,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Shade opened"
@@ -326,6 +351,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Shutter closed"
@@ -335,6 +363,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::trigger_for_name%]"
}
},
"name": "Shutter opened"

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for:
required: true
default: 00:00:00
selector:
duration:
awning_closed:
fields: *trigger_common_fields

View File

@@ -45,7 +45,7 @@ class DemoImageProcessingFace(ImageProcessingFaceEntity):
"""Return minimum confidence for send events."""
return 80
def process_image(self, image: bytes) -> None:
async def async_process_image(self, image: bytes) -> None:
"""Process image."""
demo_data = [
FaceInformation(
@@ -58,4 +58,4 @@ class DemoImageProcessingFace(ImageProcessingFaceEntity):
FaceInformation(confidence=62.53, name="Luna"),
]
self.process_faces(demo_data, 4)
self.async_process_faces(demo_data, 4)

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_home": {
@@ -126,6 +127,9 @@
"fields": {
"behavior": {
"name": "[%key:component::device_tracker::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::device_tracker::common::trigger_for_name%]"
}
},
"name": "Entered home"
@@ -135,6 +139,9 @@
"fields": {
"behavior": {
"name": "[%key:component::device_tracker::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::device_tracker::common::trigger_for_name%]"
}
},
"name": "Left home"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
entered_home: *trigger_common
left_home: *trigger_common

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_closed": {
@@ -45,6 +46,9 @@
"fields": {
"behavior": {
"name": "[%key:component::door::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::door::common::trigger_for_name%]"
}
},
"name": "Door closed"
@@ -54,6 +58,9 @@
"fields": {
"behavior": {
"name": "[%key:component::door::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::door::common::trigger_for_name%]"
}
},
"name": "Door opened"

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for:
required: true
default: 00:00:00
selector:
duration:
closed:
fields: *trigger_common_fields

View File

@@ -8,5 +8,5 @@
"iot_class": "local_polling",
"loggers": ["duco"],
"quality_scale": "bronze",
"requirements": ["python-duco-client==0.3.0"]
"requirements": ["python-duco-client==0.3.1"]
}

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_off": {
@@ -196,6 +197,9 @@
"fields": {
"behavior": {
"name": "[%key:component::fan::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::fan::common::trigger_for_name%]"
}
},
"name": "Fan turned off"
@@ -205,6 +209,9 @@
"fields": {
"behavior": {
"name": "[%key:component::fan::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::fan::common::trigger_for_name%]"
}
},
"name": "Fan turned on"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
turned_on: *trigger_common
turned_off: *trigger_common

View File

@@ -9,7 +9,7 @@
"iot_class": "local_polling",
"loggers": ["fritzconnection"],
"quality_scale": "gold",
"requirements": ["fritzconnection[qr]==1.15.1", "xmltodict==1.0.2"],
"requirements": ["fritzconnection[qr]==1.15.1", "xmltodict==1.0.4"],
"ssdp": [
{
"st": "urn:schemas-upnp-org:device:fritzbox:1"

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_closed": {
@@ -45,6 +46,9 @@
"fields": {
"behavior": {
"name": "[%key:component::garage_door::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::garage_door::common::trigger_for_name%]"
}
},
"name": "Garage door closed"
@@ -54,6 +58,9 @@
"fields": {
"behavior": {
"name": "[%key:component::garage_door::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::garage_door::common::trigger_for_name%]"
}
},
"name": "Garage door opened"

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for:
required: true
default: 00:00:00
selector:
duration:
closed:
fields: *trigger_common_fields

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_closed": {
@@ -45,6 +46,9 @@
"fields": {
"behavior": {
"name": "[%key:component::gate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::gate::common::trigger_for_name%]"
}
},
"name": "Gate closed"
@@ -54,6 +58,9 @@
"fields": {
"behavior": {
"name": "[%key:component::gate::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::gate::common::trigger_for_name%]"
}
},
"name": "Gate opened"

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for:
required: true
default: 00:00:00
selector:
duration:
closed:
fields: *trigger_common_fields

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

@@ -2,7 +2,8 @@
"common": {
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_drying": {
@@ -211,6 +212,9 @@
"behavior": {
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::humidifier::common::trigger_for_name%]"
},
"mode": {
"description": "The operation modes to trigger on.",
"name": "Mode"
@@ -223,6 +227,9 @@
"fields": {
"behavior": {
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::humidifier::common::trigger_for_name%]"
}
},
"name": "Humidifier started drying"
@@ -232,6 +239,9 @@
"fields": {
"behavior": {
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::humidifier::common::trigger_for_name%]"
}
},
"name": "Humidifier started humidifying"
@@ -241,6 +251,9 @@
"fields": {
"behavior": {
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::humidifier::common::trigger_for_name%]"
}
},
"name": "Humidifier turned off"
@@ -250,6 +263,9 @@
"fields": {
"behavior": {
"name": "[%key:component::humidifier::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::humidifier::common::trigger_for_name%]"
}
},
"name": "Humidifier turned on"

View File

@@ -13,6 +13,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
started_drying: *trigger_common
started_humidifying: *trigger_common
@@ -23,6 +28,7 @@ mode_changed:
target: *trigger_humidifier_target
fields:
behavior: *trigger_behavior
for: *trigger_for
mode:
context:
filter_target: target

View File

@@ -3,6 +3,7 @@
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -51,6 +52,9 @@
"behavior": {
"name": "[%key:component::humidity::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::humidity::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::humidity::common::trigger_threshold_name%]"
}

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
.humidity_threshold_entity: &humidity_threshold_entity
- domain: input_number
@@ -47,6 +52,7 @@ crossed_threshold:
target: *trigger_target
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

View File

@@ -3,6 +3,7 @@
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -68,6 +69,9 @@
"fields": {
"behavior": {
"name": "[%key:component::illuminance::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::illuminance::common::trigger_for_name%]"
}
},
"name": "Light cleared"
@@ -78,6 +82,9 @@
"behavior": {
"name": "[%key:component::illuminance::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::illuminance::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::illuminance::common::trigger_threshold_name%]"
}
@@ -89,6 +96,9 @@
"fields": {
"behavior": {
"name": "[%key:component::illuminance::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::illuminance::common::trigger_for_name%]"
}
},
"name": "Light detected"

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
.illuminance_threshold_entity: &illuminance_threshold_entity
- domain: input_number
@@ -55,6 +60,7 @@ crossed_threshold:
target: *trigger_numerical_target
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

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

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/infrared",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["infrared-protocols==1.1.0"]
"requirements": ["infrared-protocols==1.2.0"]
}

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_docked": {
@@ -98,6 +99,9 @@
"fields": {
"behavior": {
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::lawn_mower::common::trigger_for_name%]"
}
},
"name": "Lawn mower returned to dock"
@@ -107,6 +111,9 @@
"fields": {
"behavior": {
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::lawn_mower::common::trigger_for_name%]"
}
},
"name": "Lawn mower encountered an error"
@@ -116,6 +123,9 @@
"fields": {
"behavior": {
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::lawn_mower::common::trigger_for_name%]"
}
},
"name": "Lawn mower paused mowing"
@@ -125,6 +135,9 @@
"fields": {
"behavior": {
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::lawn_mower::common::trigger_for_name%]"
}
},
"name": "Lawn mower started mowing"
@@ -134,6 +147,9 @@
"fields": {
"behavior": {
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::lawn_mower::common::trigger_for_name%]"
}
},
"name": "Lawn mower started returning to dock"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
docked: *trigger_common
errored: *trigger_common

View File

@@ -36,6 +36,7 @@
"field_xy_color_name": "XY-color",
"section_advanced_fields_name": "Advanced options",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -515,6 +516,9 @@
"behavior": {
"name": "[%key:component::light::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::light::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::light::common::trigger_threshold_name%]"
}
@@ -526,6 +530,9 @@
"fields": {
"behavior": {
"name": "[%key:component::light::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::light::common::trigger_for_name%]"
}
},
"name": "Light turned off"
@@ -535,6 +542,9 @@
"fields": {
"behavior": {
"name": "[%key:component::light::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::light::common::trigger_for_name%]"
}
},
"name": "Light turned on"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
.brightness_threshold_entity: &brightness_threshold_entity
- domain: input_number
@@ -46,6 +51,7 @@ brightness_crossed_threshold:
target: *trigger_light_target
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_jammed": {
@@ -146,6 +147,9 @@
"fields": {
"behavior": {
"name": "[%key:component::lock::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::lock::common::trigger_for_name%]"
}
},
"name": "Lock jammed"
@@ -155,6 +159,9 @@
"fields": {
"behavior": {
"name": "[%key:component::lock::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::lock::common::trigger_for_name%]"
}
},
"name": "Lock locked"
@@ -164,6 +171,9 @@
"fields": {
"behavior": {
"name": "[%key:component::lock::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::lock::common::trigger_for_name%]"
}
},
"name": "Lock opened"
@@ -173,6 +183,9 @@
"fields": {
"behavior": {
"name": "[%key:component::lock::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::lock::common::trigger_for_name%]"
}
},
"name": "Lock unlocked"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
jammed: *trigger_common
locked: *trigger_common

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_not_playing": {
@@ -438,6 +439,9 @@
"fields": {
"behavior": {
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::media_player::common::trigger_for_name%]"
}
},
"name": "Media player paused playing"
@@ -447,6 +451,9 @@
"fields": {
"behavior": {
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::media_player::common::trigger_for_name%]"
}
},
"name": "Media player started playing"
@@ -456,6 +463,9 @@
"fields": {
"behavior": {
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::media_player::common::trigger_for_name%]"
}
},
"name": "Media player stopped playing"
@@ -465,6 +475,9 @@
"fields": {
"behavior": {
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::media_player::common::trigger_for_name%]"
}
},
"name": "Media player turned off"
@@ -474,6 +487,9 @@
"fields": {
"behavior": {
"name": "[%key:component::media_player::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::media_player::common::trigger_for_name%]"
}
},
"name": "Media player turned on"

View File

@@ -13,6 +13,11 @@
- first
- last
- any
for:
required: true
default: 00:00:00
selector:
duration:
paused_playing: *trigger_common
started_playing: *trigger_common

View File

@@ -3,6 +3,7 @@
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -68,6 +69,9 @@
"fields": {
"behavior": {
"name": "[%key:component::moisture::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::moisture::common::trigger_for_name%]"
}
},
"name": "Moisture cleared"
@@ -78,6 +82,9 @@
"behavior": {
"name": "[%key:component::moisture::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::moisture::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::moisture::common::trigger_threshold_name%]"
}
@@ -89,6 +96,9 @@
"fields": {
"behavior": {
"name": "[%key:component::moisture::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::moisture::common::trigger_for_name%]"
}
},
"name": "Moisture detected"

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
.moisture_threshold_entity: &moisture_threshold_entity
- domain: input_number
@@ -57,6 +62,7 @@ crossed_threshold:
target: *trigger_numerical_target
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

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

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_detected": {
@@ -45,6 +46,9 @@
"fields": {
"behavior": {
"name": "[%key:component::occupancy::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::occupancy::common::trigger_for_name%]"
}
},
"name": "Occupancy cleared"
@@ -54,6 +58,9 @@
"fields": {
"behavior": {
"name": "[%key:component::occupancy::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::occupancy::common::trigger_for_name%]"
}
},
"name": "Occupancy detected"

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for:
required: true
default: 00:00:00
selector:
duration:
detected:
fields: *trigger_common_fields

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_home": {
@@ -77,6 +78,9 @@
"fields": {
"behavior": {
"name": "[%key:component::person::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::person::common::trigger_for_name%]"
}
},
"name": "Entered home"
@@ -86,6 +90,9 @@
"fields": {
"behavior": {
"name": "[%key:component::person::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::person::common::trigger_for_name%]"
}
},
"name": "Left home"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
entered_home: *trigger_common
left_home: *trigger_common

View File

@@ -3,6 +3,7 @@
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -51,6 +52,9 @@
"behavior": {
"name": "[%key:component::power::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::power::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::power::common::trigger_threshold_name%]"
}

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
.power_units: &power_units
- "mW"
@@ -49,6 +54,7 @@ crossed_threshold:
target: *trigger_target
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_off": {
@@ -159,6 +160,9 @@
"fields": {
"behavior": {
"name": "[%key:component::remote::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::remote::common::trigger_for_name%]"
}
},
"name": "Remote turned off"
@@ -168,6 +172,9 @@
"fields": {
"behavior": {
"name": "[%key:component::remote::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::remote::common::trigger_for_name%]"
}
},
"name": "Remote turned on"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
turned_off: *trigger_common
turned_on: *trigger_common

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/rest",
"integration_type": "service",
"iot_class": "local_polling",
"requirements": ["jsonpath==0.82.2", "xmltodict==1.0.2"]
"requirements": ["jsonpath==0.82.2", "xmltodict==1.0.4"]
}

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_off": {
@@ -76,6 +77,9 @@
"fields": {
"behavior": {
"name": "[%key:component::schedule::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::schedule::common::trigger_for_name%]"
}
},
"name": "Schedule block ended"
@@ -85,6 +89,9 @@
"fields": {
"behavior": {
"name": "[%key:component::schedule::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::schedule::common::trigger_for_name%]"
}
},
"name": "Schedule block started"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
turned_off: *trigger_common
turned_on: *trigger_common

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_off": {
@@ -87,6 +88,9 @@
"fields": {
"behavior": {
"name": "[%key:component::siren::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::siren::common::trigger_for_name%]"
}
},
"name": "Siren turned off"
@@ -96,6 +100,9 @@
"fields": {
"behavior": {
"name": "[%key:component::siren::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::siren::common::trigger_for_name%]"
}
},
"name": "Siren turned on"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
turned_off: *trigger_common
turned_on: *trigger_common

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

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/startca",
"iot_class": "cloud_polling",
"quality_scale": "legacy",
"requirements": ["xmltodict==1.0.2"]
"requirements": ["xmltodict==1.0.4"]
}

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

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_off": {
@@ -101,6 +102,9 @@
"fields": {
"behavior": {
"name": "[%key:component::switch::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::switch::common::trigger_for_name%]"
}
},
"name": "Switch turned off"
@@ -110,6 +114,9 @@
"fields": {
"behavior": {
"name": "[%key:component::switch::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::switch::common::trigger_for_name%]"
}
},
"name": "Switch turned on"

View File

@@ -14,6 +14,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
turned_off: *trigger_common
turned_on: *trigger_common

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/ted5000",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["xmltodict==1.0.2"]
"requirements": ["xmltodict==1.0.4"]
}

View File

@@ -3,6 +3,7 @@
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -51,6 +52,9 @@
"behavior": {
"name": "[%key:component::temperature::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::temperature::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::temperature::common::trigger_threshold_name%]"
}

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
.temperature_units: &temperature_units
- "°C"
@@ -47,6 +52,7 @@ crossed_threshold:
target: *trigger_target
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_available": {
@@ -125,6 +126,9 @@
"fields": {
"behavior": {
"name": "[%key:component::update::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::update::common::trigger_for_name%]"
}
},
"name": "Update became available"

View File

@@ -13,5 +13,10 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
update_became_available: *trigger_common

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable, Coroutine, Sequence
import dataclasses
from datetime import datetime, timedelta
import logging
import os
@@ -167,6 +168,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await usb_discovery.async_setup()
hass.data[_USB_DATA] = usb_discovery
websocket_api.async_register_command(hass, websocket_usb_scan)
websocket_api.async_register_command(hass, websocket_usb_list_serial_ports)
return True
@@ -481,3 +483,23 @@ async def websocket_usb_scan(
"""Scan for new usb devices."""
await async_request_scan(hass)
connection.send_result(msg["id"])
@websocket_api.require_admin
@websocket_api.websocket_command({vol.Required("type"): "usb/list_serial_ports"})
@websocket_api.async_response
async def websocket_usb_list_serial_ports(
hass: HomeAssistant,
connection: ActiveConnection,
msg: dict[str, Any],
) -> None:
"""List available serial ports."""
try:
ports = await async_scan_serial_ports(hass)
except OSError as err:
connection.send_error(msg["id"], websocket_api.ERR_UNKNOWN_ERROR, str(err))
return
connection.send_result(
msg["id"],
[dataclasses.asdict(port) for port in ports],
)

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_cleaning": {
@@ -190,6 +191,9 @@
"fields": {
"behavior": {
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::vacuum::common::trigger_for_name%]"
}
},
"name": "Vacuum returned to dock"
@@ -199,6 +203,9 @@
"fields": {
"behavior": {
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::vacuum::common::trigger_for_name%]"
}
},
"name": "Vacuum encountered an error"
@@ -208,6 +215,9 @@
"fields": {
"behavior": {
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::vacuum::common::trigger_for_name%]"
}
},
"name": "Vacuum cleaner paused cleaning"
@@ -217,6 +227,9 @@
"fields": {
"behavior": {
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::vacuum::common::trigger_for_name%]"
}
},
"name": "Vacuum cleaner started cleaning"
@@ -226,6 +239,9 @@
"fields": {
"behavior": {
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::vacuum::common::trigger_for_name%]"
}
},
"name": "Vacuum cleaner started returning to dock"

View File

@@ -13,6 +13,11 @@
- last
- any
translation_key: trigger_behavior
for:
required: true
default: 00:00:00
selector:
duration:
docked: *trigger_common
errored: *trigger_common

View File

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_closed": {
@@ -96,6 +97,9 @@
"fields": {
"behavior": {
"name": "[%key:component::valve::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::valve::common::trigger_for_name%]"
}
},
"name": "Valve closed"
@@ -105,6 +109,9 @@
"fields": {
"behavior": {
"name": "[%key:component::valve::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::valve::common::trigger_for_name%]"
}
},
"name": "Valve opened"

View File

@@ -13,6 +13,11 @@
- first
- last
- any
for:
required: true
default: 00:00:00
selector:
duration:
closed: *trigger_common
opened: *trigger_common

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

@@ -3,6 +3,7 @@
"condition_behavior_name": "Condition passes if",
"condition_threshold_name": "Threshold type",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least",
"trigger_threshold_name": "Threshold type"
},
"conditions": {
@@ -184,6 +185,9 @@
"behavior": {
"name": "[%key:component::water_heater::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::water_heater::common::trigger_for_name%]"
},
"operation_mode": {
"description": "The operation modes to trigger on.",
"name": "Operation mode"
@@ -206,6 +210,9 @@
"behavior": {
"name": "[%key:component::water_heater::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::water_heater::common::trigger_for_name%]"
},
"threshold": {
"name": "[%key:component::water_heater::common::trigger_threshold_name%]"
}
@@ -217,6 +224,9 @@
"fields": {
"behavior": {
"name": "[%key:component::water_heater::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::water_heater::common::trigger_for_name%]"
}
},
"name": "Water heater turned off"
@@ -226,6 +236,9 @@
"fields": {
"behavior": {
"name": "[%key:component::water_heater::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::water_heater::common::trigger_for_name%]"
}
},
"name": "Water heater turned on"

View File

@@ -13,6 +13,11 @@
- first
- last
- any
for: &trigger_for
required: true
default: 00:00:00
selector:
duration:
.temperature_units: &temperature_units
- "°C"
@@ -30,6 +35,7 @@ operation_mode_changed:
target: *trigger_water_heater_target
fields:
behavior: *trigger_behavior
for: *trigger_for
operation_mode:
context:
filter_target: target
@@ -62,6 +68,7 @@ target_temperature_crossed_threshold:
target: *trigger_water_heater_target
fields:
behavior: *trigger_behavior
for: *trigger_for
threshold:
required: true
selector:

View File

@@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"loggers": ["waterfurnace"],
"quality_scale": "bronze",
"requirements": ["waterfurnace==1.6.2"]
"requirements": ["waterfurnace==1.6.5"]
}

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

@@ -1,7 +1,8 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"trigger_behavior_name": "Trigger when"
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
"conditions": {
"is_closed": {
@@ -45,6 +46,9 @@
"fields": {
"behavior": {
"name": "[%key:component::window::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::window::common::trigger_for_name%]"
}
},
"name": "Window closed"
@@ -54,6 +58,9 @@
"fields": {
"behavior": {
"name": "[%key:component::window::common::trigger_behavior_name%]"
},
"for": {
"name": "[%key:component::window::common::trigger_for_name%]"
}
},
"name": "Window opened"

View File

@@ -9,6 +9,11 @@
- first
- last
- any
for:
required: true
default: 00:00:00
selector:
duration:
closed:
fields: *trigger_common_fields

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/zestimate",
"iot_class": "cloud_polling",
"quality_scale": "legacy",
"requirements": ["xmltodict==1.0.2"]
"requirements": ["xmltodict==1.0.4"]
}

View File

@@ -804,6 +804,6 @@ def _decode_jwt(hass: HomeAssistant, encoded: str) -> dict[str, Any] | None:
return None
try:
return jwt.decode(encoded, secret, algorithms=["HS256"]) # type: ignore[no-any-return]
return jwt.decode(encoded, secret, algorithms=["HS256"])
except jwt.InvalidTokenError:
return None

View File

@@ -1771,6 +1771,28 @@ class SelectSelector(Selector[SelectSelectorConfig]):
return [parent_schema(vol.Schema(str)(val)) for val in data]
class SerialSelectorConfig(BaseSelectorConfig):
"""Class to represent a serial selector config."""
@SELECTORS.register("serial")
class SerialSelector(Selector[SerialSelectorConfig]):
"""Selector for a serial port."""
selector_type = "serial"
CONFIG_SCHEMA = make_selector_config_schema()
def __init__(self, config: SerialSelectorConfig | None = None) -> None:
"""Instantiate a selector."""
super().__init__(config)
def __call__(self, data: Any) -> str:
"""Validate the passed selection."""
serial: str = vol.Schema(str)(data)
return serial
class StateSelectorConfig(BaseSelectorConfig, total=False):
"""Class to represent an state selector config."""

View File

@@ -16,7 +16,7 @@ async-interrupt==1.2.2
async-upnp-client==0.46.2
atomicwrites-homeassistant==1.4.1
attrs==26.1.0
audioop-lts==0.2.1
audioop-lts==0.2.2
av==16.0.1
awesomeversion==25.8.0
bcrypt==5.0.0
@@ -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
@@ -53,13 +53,13 @@ paho-mqtt==2.1.0
Pillow==12.2.0
propcache==0.4.1
psutil-home-assistant==0.0.1
PyJWT==2.10.1
PyJWT==2.12.1
pymicro-vad==1.0.1
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

@@ -38,7 +38,7 @@ dependencies = [
"async-interrupt==1.2.2",
"attrs==26.1.0",
"atomicwrites-homeassistant==1.4.1",
"audioop-lts==0.2.1",
"audioop-lts==0.2.2",
"awesomeversion==25.8.0",
"bcrypt==5.0.0",
"certifi>=2021.5.30",
@@ -51,11 +51,11 @@ 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",
"PyJWT==2.10.1",
"lru-dict==1.4.1",
"PyJWT==2.12.1",
# PyJWT has loose dependency. We want the latest one.
"cryptography==46.0.7",
"Pillow==12.2.0",

12
requirements.txt generated
View File

@@ -15,7 +15,7 @@ astral==2.2
async-interrupt==1.2.2
atomicwrites-homeassistant==1.4.1
attrs==26.1.0
audioop-lts==0.2.1
audioop-lts==0.2.2
awesomeversion==25.8.0
bcrypt==5.0.0
certifi>=2021.5.30
@@ -26,25 +26,25 @@ 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.1.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
Pillow==12.2.0
propcache==0.4.1
psutil-home-assistant==0.0.1
PyJWT==2.10.1
PyJWT==2.12.1
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

14
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
@@ -1328,7 +1328,7 @@ influxdb-client==1.50.0
influxdb==5.3.1
# homeassistant.components.infrared
infrared-protocols==1.1.0
infrared-protocols==1.2.0
# homeassistant.components.inkbird
inkbird-ble==1.1.1
@@ -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
@@ -2562,7 +2562,7 @@ python-digitalocean==1.13.2
python-dropbox-api==0.1.3
# homeassistant.components.duco
python-duco-client==0.3.0
python-duco-client==0.3.1
# homeassistant.components.ecobee
python-ecobee-api==0.3.2
@@ -3273,7 +3273,7 @@ wallbox==0.9.0
watchdog==6.0.0
# homeassistant.components.waterfurnace
waterfurnace==1.6.2
waterfurnace==1.6.5
# homeassistant.components.watergate
watergate-local-api==2025.1.0
@@ -3334,7 +3334,7 @@ xknxproject==3.8.2
# homeassistant.components.startca
# homeassistant.components.ted5000
# homeassistant.components.zestimate
xmltodict==1.0.2
xmltodict==1.0.4
# homeassistant.components.xs1
xs1-api-client==3.0.0

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
@@ -1180,7 +1180,7 @@ influxdb-client==1.50.0
influxdb==5.3.1
# homeassistant.components.infrared
infrared-protocols==1.1.0
infrared-protocols==1.2.0
# homeassistant.components.inkbird
inkbird-ble==1.1.1
@@ -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
@@ -2185,7 +2185,7 @@ python-bsblan==5.1.4
python-dropbox-api==0.1.3
# homeassistant.components.duco
python-duco-client==0.3.0
python-duco-client==0.3.1
# homeassistant.components.ecobee
python-ecobee-api==0.3.2
@@ -2773,7 +2773,7 @@ wallbox==0.9.0
watchdog==6.0.0
# homeassistant.components.waterfurnace
waterfurnace==1.6.2
waterfurnace==1.6.5
# homeassistant.components.watergate
watergate-local-api==2025.1.0
@@ -2828,7 +2828,7 @@ xknxproject==3.8.2
# homeassistant.components.startca
# homeassistant.components.ted5000
# homeassistant.components.zestimate
xmltodict==1.0.2
xmltodict==1.0.4
# homeassistant.components.yale_smart_alarm
yalesmartalarmclient==0.4.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,7 +7,6 @@ from homeassistant import core
from homeassistant.util import executor, thread
from .model import Config, Integration
from .requirements import PACKAGE_REGEX, PIP_VERSION_RANGE_SEPARATOR
_GO2RTC_SHA = (
"675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae" # 1.9.14
@@ -43,8 +42,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 +62,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 \
@@ -139,10 +137,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 +152,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 +166,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,27 +185,16 @@ 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(
timeout=timeout,
**package_versions,
go2rtc=_GO2RTC_SHA,
),
config.root / "Dockerfile",
),
File(
_HASSFEST_TEMPLATE.format(
timeout=timeout,
**package_versions,
),
_HASSFEST_TEMPLATE.format(),
config.root / "script/hassfest/docker/Dockerfile",
),
]

View File

@@ -11,10 +11,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 +26,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__

Some files were not shown because too many files have changed in this diff Show More