From 77a885a35608b967e582b20dbe3869cb2e3748e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Szypowicz?= <2733699+pszypowicz@users.noreply.github.com> Date: Fri, 1 May 2026 23:43:18 +0200 Subject: [PATCH] Silence ERROR log on override_properties recovery path override_properties is designed to recover gracefully when the characteristic's existing value falls outside a newly-restricted valid_values set (e.g. SecuritySystemCurrentState defaults to StayArmed=0, but a consumer configures the characteristic for an alarm panel that does not advertise ARM_HOME and so removes 0 from valid_values). The recovery is silent from the caller's perspective: ValueError is caught and self.value is reset via _get_default_value(). The validator it relied on, valid_value_or_raise, still calls logger.error() before raising, leaking an "is an invalid value" line into the application log even though the situation is expected and recovered. Downstream this manifests in Home Assistant as spurious pyhap.characteristic ERRORs at every HomeKit accessory build for alarm panels that restrict StayArmed (issue ikalchev/HAP-python#473 and home-assistant/core#130564, #156142). Replace the try/except-around-validator pattern in override_properties with an explicit silent membership check. valid_value_or_raise itself is unchanged so callers like set_value (where ERROR-level logging is appropriate) keep their behavior. --- pyhap/characteristic.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyhap/characteristic.py b/pyhap/characteristic.py index ee768fe8..46a28562 100644 --- a/pyhap/characteristic.py +++ b/pyhap/characteristic.py @@ -320,10 +320,16 @@ def override_properties( return try: - self.value = self.to_valid_value(self._value) - self.valid_value_or_raise(self._value) + candidate = self.to_valid_value(self._value) except ValueError: self.value = self._get_default_value() + return + + valid_values_dict = self._properties.get(PROP_VALID_VALUES) + if valid_values_dict and candidate not in valid_values_dict.values(): + self.value = self._get_default_value() + else: + self.value = candidate def _clear_cache(self) -> None: """Clear the cached HAP representation."""