-
Notifications
You must be signed in to change notification settings - Fork 119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added getter_callback to Characteristic #90
Changes from 7 commits
9d9a240
4148873
6c46754
e9b16f7
cece383
9750db2
7851399
781aad9
49a4e07
9bb6cf4
c1c4ade
4e7ae11
9b2875d
d2c628b
a9d4c4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -79,7 +79,7 @@ class Characteristic: | |
""" | ||
|
||
__slots__ = ('display_name', 'type_id', 'properties', 'broker', | ||
'setter_callback', 'value') | ||
'value', 'getter_callback', 'setter_callback') | ||
|
||
def __init__(self, display_name, type_id, properties): | ||
"""Initialise with the given properties. | ||
|
@@ -98,8 +98,9 @@ def __init__(self, display_name, type_id, properties): | |
self.type_id = type_id | ||
self.properties = properties | ||
self.broker = None | ||
self.setter_callback = None | ||
self.value = self._get_default_value() | ||
self.getter_callback = None | ||
self.setter_callback = None | ||
|
||
def __repr__(self): | ||
"""Return the representation of the characteristic.""" | ||
|
@@ -114,6 +115,11 @@ def _get_default_value(self): | |
value = HAP_FORMAT_DEFAULTS[self.properties[PROP_FORMAT]] | ||
return self.to_valid_value(value) | ||
|
||
def get_value(self): | ||
if self.getter_callback: | ||
self.value = self.to_valid_value(value=self.getter_callback()) | ||
return self.value | ||
|
||
def to_valid_value(self, value): | ||
"""Perform validation and conversion to valid value""" | ||
if self.properties.get(PROP_VALID_VALUES): | ||
|
@@ -168,6 +174,12 @@ def set_value(self, value, should_notify=True): | |
If not set_value will be aborted and an error message will be | ||
displayed. | ||
|
||
`Characteristic.setter_callback` | ||
You may also define a `setter_callback` on the `Characteristic`. | ||
This will be called with the value being set as the arg. | ||
|
||
.. seealso:: Characteristic.value | ||
|
||
:param value: The value to assign as this Characteristic's value. | ||
:type value: Depends on properties["Format"] | ||
|
||
|
@@ -184,12 +196,15 @@ def set_value(self, value, should_notify=True): | |
def client_update_value(self, value): | ||
"""Called from broker for value change in Home app. | ||
|
||
Change self.value to value and call callback. | ||
Call set_value and call callback. | ||
""" | ||
logger.debug('client_update_value: %s to %s', | ||
self.display_name, value) | ||
logger.debug('%s: Client update value to %s', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed the log statement in one of the last merged PR #99 |
||
self.display_name, value) | ||
# Set new value before anything else, then notify client | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This and the comment below shouldn't be necessary. |
||
self.value = value | ||
self.notify() | ||
# Call setter_callback | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same here. It would be quite confusion if a self._value = value
self.notify() There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please revert the changes to |
||
if self.setter_callback: | ||
self.setter_callback(value) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -202,3 +202,162 @@ def test_from_dict(): | |
assert char.display_name == 'Test Char' | ||
assert char.type_id == uuid | ||
assert char.properties == {'Format': 'int', 'Permissions': 'read'} | ||
|
||
class TestCharacteristic(unittest.TestCase): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All tests in the |
||
|
||
def test_repr(self): | ||
char = get_char(PROPERTIES.copy()) | ||
del char.properties['Permissions'] | ||
self.assertEqual( | ||
'<characteristic display_name=Test Char value=0 ' \ | ||
'properties={\'Format\': \'int\'}>', char.__repr__()) | ||
|
||
def test_default_value(self): | ||
char = get_char(PROPERTIES.copy()) | ||
self.assertEqual(char.value, HAP_FORMAT_DEFAULTS[PROPERTIES['Format']]) | ||
|
||
def test_get_default_value(self): | ||
valid_values = {'foo': 2, 'bar': 3} | ||
char = get_char(PROPERTIES.copy(), valid=valid_values) | ||
self.assertEqual(char.value, 2) | ||
self.assertIn(char.value, valid_values.values()) | ||
char = get_char(PROPERTIES.copy(), min_value=3, max_value=10) | ||
self.assertEqual(char.value, 3) | ||
|
||
def test_to_valid_value(self): | ||
char = get_char(PROPERTIES.copy(), valid={'foo': 2, 'bar': 3}, | ||
min_value=2, max_value=7) | ||
with self.assertRaises(ValueError): | ||
char.to_valid_value(1) | ||
self.assertEqual(char.to_valid_value(2), 2) | ||
|
||
del char.properties['ValidValues'] | ||
for value in ('2', None): | ||
with self.assertRaises(ValueError): | ||
char.to_valid_value(value) | ||
self.assertEqual(char.to_valid_value(1), 2) | ||
self.assertEqual(char.to_valid_value(5), 5) | ||
self.assertEqual(char.to_valid_value(8), 7) | ||
|
||
char.properties['Format'] = 'string' | ||
self.assertEqual(char.to_valid_value(24), '24') | ||
|
||
char.properties['Format'] = 'bool' | ||
self.assertTrue(char.to_valid_value(1)) | ||
self.assertFalse(char.to_valid_value(0)) | ||
|
||
char.properties['Format'] = 'dictionary' | ||
self.assertEqual(char.to_valid_value({'a': 1}), {'a': 1}) | ||
|
||
def test_override_properties_properties(self): | ||
new_properties = {'minValue': 10, 'maxValue': 20, 'step': 1} | ||
char = get_char(PROPERTIES.copy(), min_value=0, max_value=1) | ||
char.override_properties(properties=new_properties) | ||
self.assertEqual(char.properties['minValue'], | ||
new_properties['minValue']) | ||
self.assertEqual(char.properties['maxValue'], | ||
new_properties['maxValue']) | ||
self.assertEqual(char.properties['step'], new_properties['step']) | ||
|
||
def test_override_properties_valid_values(self): | ||
new_valid_values = {'foo2': 2, 'bar2': 3} | ||
char = get_char(PROPERTIES.copy(), valid={'foo': 1, 'bar': 2}) | ||
char.override_properties(valid_values=new_valid_values) | ||
self.assertEqual(char.properties['ValidValues'], new_valid_values) | ||
|
||
def test_override_properties_error(self): | ||
char = get_char(PROPERTIES.copy()) | ||
with self.assertRaises(ValueError): | ||
char.override_properties() | ||
|
||
def test_set_value(self): | ||
path = 'pyhap.characteristic.Characteristic.notify' | ||
char = get_char(PROPERTIES.copy(), min_value=3, max_value=7) | ||
|
||
with patch(path) as mock_notify: | ||
char.set_value(5) | ||
self.assertEqual(char.value, 5) | ||
self.assertFalse(mock_notify.called) | ||
|
||
char.broker = Mock() | ||
char.set_value(8, should_notify=False) | ||
self.assertEqual(char.value, 7) | ||
self.assertFalse(mock_notify.called) | ||
|
||
char.set_value(1) | ||
self.assertEqual(char.value, 3) | ||
self.assertEqual(mock_notify.call_count, 1) | ||
|
||
def test_client_update_value(self): | ||
path_notify = 'pyhap.characteristic.Characteristic.notify' | ||
char = get_char(PROPERTIES.copy()) | ||
|
||
with patch(path_notify) as mock_notify: | ||
char.client_update_value(4) | ||
self.assertEqual(char.value, 4) | ||
with patch.object(char, 'setter_callback') as mock_callback: | ||
char.client_update_value(3) | ||
|
||
self.assertEqual(char.value, 3) | ||
self.assertEqual(mock_notify.call_count, 2) | ||
mock_callback.assert_called_with(3) | ||
|
||
def test_notify(self): | ||
char = get_char(PROPERTIES.copy()) | ||
|
||
char.value = 2 | ||
with self.assertRaises(AttributeError): | ||
char.notify() | ||
|
||
with patch.object(char, 'broker') as mock_broker: | ||
char.notify() | ||
mock_broker.publish.assert_called_with(2, char) | ||
|
||
def test_to_HAP_numberic(self): | ||
char = get_char(PROPERTIES.copy(), min_value=1, max_value=2) | ||
with patch.object(char, 'broker') as mock_broker: | ||
mock_iid = mock_broker.iid_manager.get_iid | ||
mock_iid.return_value = 2 | ||
hap_repr = char.to_HAP() | ||
mock_iid.assert_called_with(char) | ||
|
||
self.assertEqual( | ||
hap_repr, | ||
{ | ||
'iid': 2, | ||
'type': ANY, | ||
'description': 'Test Char', | ||
'perms': ['pr'], | ||
'format': 'int', | ||
'maxValue': 2, | ||
'minValue': 1, | ||
'value': 1, | ||
}) | ||
|
||
def test_to_HAP_string(self): | ||
char = get_char(PROPERTIES.copy()) | ||
char.properties['Format'] = 'string' | ||
char.value = 'aaa' | ||
with patch.object(char, 'broker') as mock_broker: | ||
hap_repr = char.to_HAP() | ||
self.assertEqual(hap_repr['format'], 'string') | ||
self.assertNotIn('maxLen', hap_repr) | ||
|
||
char.value = 'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee' \ | ||
'ffffffffffgggggggggg' | ||
with patch.object(char, 'broker') as mock_broker: | ||
hap_repr = char.to_HAP() | ||
self.assertEqual(hap_repr['maxLen'], 70) | ||
self.assertEqual(hap_repr['value'], char.value) | ||
|
||
def test_to_HAP_bool(self): | ||
char = get_char(PROPERTIES.copy()) | ||
char.properties['Format'] = 'bool' | ||
with patch.object(char, 'broker') as mock_broker: | ||
hap_repr = char.to_HAP() | ||
self.assertEqual(hap_repr['format'], 'bool') | ||
|
||
char.properties['Permissions'] = [] | ||
with patch.object(char, 'broker') as mock_broker: | ||
hap_repr = char.to_HAP() | ||
self.assertNotIn('value', hap_repr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation stating that this calls the getter if any to update the .value