diff --git a/src/fastcs/attributes/attr_r.py b/src/fastcs/attributes/attr_r.py index aec7f522..788aeefe 100644 --- a/src/fastcs/attributes/attr_r.py +++ b/src/fastcs/attributes/attr_r.py @@ -38,7 +38,9 @@ def __init__( ) self._update_callback: AttrIOUpdateCallback[DType_T] | None = None """Callback to update the value of the attribute with an IO to the source""" - self._on_update_callbacks: list[AttrOnUpdateCallback[DType_T]] | None = None + self._on_update_callbacks: ( + list[tuple[AttrOnUpdateCallback[DType_T], bool]] | None + ) = None """Callbacks to publish changes to the value of the attribute""" self._on_update_events: set[PredicateEvent[DType_T]] = set() """Events to set when the value satisifies some predicate""" @@ -71,6 +73,7 @@ async def update(self, value: Any) -> None: "Attribute set", value=value, value_type=type(value), attribute=self ) + _previous_value = self._value self._value = self._datatype.validate(value) self._on_update_events -= { @@ -78,17 +81,22 @@ async def update(self, value: Any) -> None: } if self._on_update_callbacks is not None: + callbacks_to_call: list[AttrOnUpdateCallback[DType_T]] = [ + cb + for cb, always in self._on_update_callbacks + if always or not self.datatype.equal(self._value, _previous_value) + ] try: - await asyncio.gather( - *[cb(self._value) for cb in self._on_update_callbacks] - ) + await asyncio.gather(*[cb(self._value) for cb in callbacks_to_call]) except Exception as e: logger.opt(exception=e).error( "On update callbacks failed", attribute=self, value=value ) raise - def add_on_update_callback(self, callback: AttrOnUpdateCallback[DType_T]) -> None: + def add_on_update_callback( + self, callback: AttrOnUpdateCallback[DType_T], always: bool = False + ) -> None: """Add a callback to be called when the value of the attribute is updated The callback will be called with the updated value. @@ -96,7 +104,7 @@ def add_on_update_callback(self, callback: AttrOnUpdateCallback[DType_T]) -> Non """ if self._on_update_callbacks is None: self._on_update_callbacks = [] - self._on_update_callbacks.append(callback) + self._on_update_callbacks.append((callback, always)) def set_update_callback(self, callback: AttrIOUpdateCallback[DType_T]): """Set the callback to update the value of the attribute from the source diff --git a/tests/transports/epics/pva/test_p4p.py b/tests/transports/epics/pva/test_p4p.py index 6c013c6e..3a1a06ac 100644 --- a/tests/transports/epics/pva/test_p4p.py +++ b/tests/transports/epics/pva/test_p4p.py @@ -232,8 +232,9 @@ async def _wait_and_set_attr_r(): await controller.a.update(40_000) await controller.b.update(-0.99) await asyncio.sleep(0.05) - await controller.a.update(-100) await controller.b.update(-0.99) + # Identical value, so will not cause a readback update + await controller.a.update(-100) await controller.b.update(-0.9111111) a_values, b_values = [], [] @@ -253,7 +254,7 @@ async def _wait_and_set_attr_r(): serve.cancel() wait_and_set_attr_r.cancel() assert a_values == [0, 40_000, -100] - assert b_values == [0.0, -0.99, -0.99, -0.91] # Last is -0.91 because of prec + assert b_values == [0.0, -0.99, -0.91] # Last is -0.91 because of prec def test_pvi_grouping():