Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/fastcs/attributes/attr_r.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down Expand Up @@ -71,32 +73,38 @@ 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 -= {
e for e in self._on_update_events if e.set(self._value)
}

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.

"""
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
Expand Down
5 changes: 3 additions & 2 deletions tests/transports/epics/pva/test_p4p.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [], []
Expand All @@ -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():
Expand Down