Skip to content

Commit

Permalink
Merge pull request #392 from bdraco/allow_invalid_client_values
Browse files Browse the repository at this point in the history
  • Loading branch information
ikalchev committed Dec 2, 2021
2 parents 7f14eb1 + 626c033 commit 7b3148c
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 11 deletions.
45 changes: 35 additions & 10 deletions pyhap/characteristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,12 @@ class Characteristic:
"service",
"_uuid_str",
"_loader_display_name",
"allow_invalid_client_values",
)

def __init__(self, display_name, type_id, properties):
def __init__(
self, display_name, type_id, properties, allow_invalid_client_values=False
):
"""Initialise with the given properties.
:param display_name: Name that will be displayed for this
Expand All @@ -150,6 +153,15 @@ def __init__(self, display_name, type_id, properties):
"""
_validate_properties(properties)
self.broker = None
#
# As of iOS 15.1, Siri requests TargetHeatingCoolingState
# as Auto reguardless if its a valid value or not.
#
# Consumers of this api may wish to set allow_invalid_client_values
# to True and handle converting the Auto state to Cool or Heat
# depending on the device.
#
self.allow_invalid_client_values = allow_invalid_client_values
self.display_name = display_name
self.properties = properties
self.type_id = type_id
Expand Down Expand Up @@ -185,14 +197,22 @@ def get_value(self):
self.value = self.to_valid_value(value=self.getter_callback())
return self.value

def valid_value_or_raise(self, value):
"""Raise ValueError if PROP_VALID_VALUES is set and the value is not present."""
if self.type_id in ALWAYS_NULL:
return
valid_values = self.properties.get(PROP_VALID_VALUES)
if not valid_values:
return
if value in valid_values.values():
return
error_msg = f"{self.display_name}: value={value} is an invalid value."
logger.error(error_msg)
raise ValueError(error_msg)

def to_valid_value(self, value):
"""Perform validation and conversion to valid value."""
if self.properties.get(PROP_VALID_VALUES):
if value not in self.properties[PROP_VALID_VALUES].values():
error_msg = f"{self.display_name}: value={value} is an invalid value."
logger.error(error_msg)
raise ValueError(error_msg)
elif self.properties[PROP_FORMAT] == HAP_FORMAT_STRING:
if self.properties[PROP_FORMAT] == HAP_FORMAT_STRING:
value = str(value)[
: self.properties.get(HAP_REPR_MAX_LEN, DEFAULT_MAX_LENGTH)
]
Expand Down Expand Up @@ -241,6 +261,7 @@ def override_properties(self, properties=None, valid_values=None):

try:
self.value = self.to_valid_value(self.value)
self.valid_value_or_raise(self.value)
except ValueError:
self.value = self._get_default_value()

Expand All @@ -265,6 +286,7 @@ def set_value(self, value, should_notify=True):
"""
logger.debug("set_value: %s to %s", self.display_name, value)
value = self.to_valid_value(value)
self.valid_value_or_raise(value)
changed = self.value != value
self.value = value
if changed and should_notify and self.broker:
Expand All @@ -280,20 +302,23 @@ def client_update_value(self, value, sender_client_addr=None):
original_value = value
if self.type_id not in ALWAYS_NULL or original_value is not None:
value = self.to_valid_value(value)
if not self.allow_invalid_client_values:
self.valid_value_or_raise(value)
logger.debug(
"client_update_value: %s to %s (original: %s) from client: %s",
self.display_name,
value,
original_value,
sender_client_addr,
)
changed = self.value != value
previous_value = self.value
self.value = value
if changed:
self.notify(sender_client_addr)
if self.setter_callback:
# pylint: disable=not-callable
self.setter_callback(value)
changed = self.value != previous_value
if changed:
self.notify(sender_client_addr)
if self.type_id in ALWAYS_NULL:
self.value = None

Expand Down
14 changes: 13 additions & 1 deletion tests/test_characteristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def test_to_valid_value():
PROPERTIES.copy(), valid={"foo": 2, "bar": 3}, min_value=2, max_value=7
)
with pytest.raises(ValueError):
char.to_valid_value(1)
char.valid_value_or_raise(1)
assert char.to_valid_value(2) == 2

del char.properties["ValidValues"]
Expand Down Expand Up @@ -353,6 +353,18 @@ def test_client_update_value():
assert len(mock_notify.mock_calls) == 3


def test_client_update_value_with_invalid_value():
"""Test updating the characteristic value with call from the driver with invalid values."""
char = get_char(PROPERTIES.copy(), valid={"foo": 0, "bar": 2, "baz": 1})

with patch.object(char, "broker"):
with pytest.raises(ValueError):
char.client_update_value(4)

char.allow_invalid_client_values = True
char.client_update_value(4)


def test_notify():
"""Test if driver is notified correctly about a changed characteristic."""
char = get_char(PROPERTIES.copy())
Expand Down

0 comments on commit 7b3148c

Please sign in to comment.