Skip to content

Commit dcc4ab2

Browse files
committed
wip - async signals
1 parent a89cd9a commit dcc4ab2

3 files changed

Lines changed: 52 additions & 33 deletions

File tree

bec_lib/bec_lib/data_api/data_api.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,3 +416,17 @@ def unsubscribe(
416416
"""
417417
for plugin in self.plugins:
418418
plugin.unsubscribe(subscription_id, scan_id, callback)
419+
420+
421+
if __name__ == "__main__":
422+
from bec_lib.client import BECClient
423+
424+
def my_callback(data, metadata):
425+
print("Received data:", data)
426+
print("With metadata:", metadata)
427+
428+
client = BECClient()
429+
data_api = DataAPI(client)
430+
sub = data_api.create_subscription("test_scan")
431+
sub.add_device(device_name="waveform", device_entry="waveform_data")
432+
sub.set_callback(my_callback)

bec_lib/bec_lib/data_api/plugins.py

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -295,13 +295,28 @@ def _device_entry_is_async_signal(self, device_name: str, device_entry: str) ->
295295
Returns:
296296
True if the device entry is an async signal, False otherwise.
297297
"""
298+
async_signal_info = self._get_async_signal_info(device_name, device_entry)
299+
return async_signal_info is not None
300+
301+
def _get_async_signal_info(self, device_name: str, device_entry: str) -> dict | None:
302+
"""
303+
Get the async signal information for the given device and entry.
304+
305+
Args:
306+
device_name: Name of the device.
307+
device_entry: Specific entry of the device.
308+
Returns:
309+
The async signal information dict if found, None otherwise.
310+
"""
298311
if not self.client.device_manager:
299-
return False
300-
async_signals = self.client.device_manager.get_bec_signals("AsyncSignal")
301-
for entry_name, _, entry_data in async_signals:
302-
if entry_name == device_entry and entry_data.get("device_name") == device_name:
303-
return True
304-
return False
312+
return None
313+
async_signals = self.client.device_manager.get_bec_signals(
314+
["AsyncSignal", "AsyncMultiSignal", "DynamicSignal"]
315+
)
316+
for dev_name, _, entry_info in async_signals:
317+
if entry_info.get("obj_name") == device_entry and dev_name == device_name:
318+
return entry_info
319+
return None
305320

306321
def subscribe(
307322
self,
@@ -665,9 +680,16 @@ def _subscribe_to_async_signal(
665680
async_sub.callback_refs.append(callback_ref)
666681
else:
667682
# Create new redis connector subscription
683+
async_signal_info = self._get_async_signal_info(device_name, device_entry)
684+
if async_signal_info is None:
685+
raise ValueError(
686+
f"Cannot subscribe to async signal '{device_name}' entry '{device_entry}': signal not found."
687+
)
668688
connector_id = self.client.connector.register(
669689
MessageEndpoints.device_async_signal(
670-
scan_id=scan_id, device=device_name, signal=device_entry
690+
scan_id=scan_id,
691+
device=device_name,
692+
signal=async_signal_info.get("storage_name"),
671693
),
672694
cb=self._async_signal_sync_callback,
673695
from_start=True,
@@ -783,11 +805,7 @@ def _get_expected_device_count(self, callback_ref: CallbackRef, scan_id: str) ->
783805
count += len(self._monitored_subscriptions[scan_id][callback_ref].devices)
784806

785807
# Count async signals
786-
for (
787-
sub_scan_id,
788-
device_name,
789-
device_entry,
790-
), async_sub in self._async_subscriptions.items():
808+
for (sub_scan_id, _, _), async_sub in self._async_subscriptions.items():
791809
if sub_scan_id == scan_id and callback_ref in async_sub.callback_refs:
792810
count += 1
793811

@@ -854,16 +872,3 @@ def _check_and_emit_synchronized_data(self, callback_ref: CallbackRef, scan_id:
854872

855873
# Update the min_length to track what we've already emitted
856874
callback_buffer.min_length = min_length
857-
858-
859-
"""
860-
NOTES
861-
862-
- AsyncSignal subscriptions should be shared between multiple subscribers to avoid redundant subscriptions.
863-
- Whenever the redis connector triggers the callback, we broadcast to all subscribers of that device/entry/scan combination.
864-
- When the user subscribed to an AsyncSignal, we check if there is already a subscription for that device/entry/scan combination.
865-
- If yes, we just add the callback to the list of callbacks for that subscription.
866-
- When the user subscribes to multiple AsyncSignals, we synchronize the data length and only broadcast the data of equal length. Same for mixtures
867-
of monitored devices and AsyncSignals.
868-
869-
"""

bec_lib/tests/test_data_api.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,8 @@ def test_device_entry_is_monitored(self, mock_client, scan_item_with_monitored_d
308308
def test_device_entry_is_async_signal(self, mock_client):
309309
"""Test detection of async signal device entries."""
310310
mock_client.device_manager.get_bec_signals.return_value = [
311-
("async_sig1", None, {"device_name": "detector1"}),
312-
("async_sig2", None, {"device_name": "detector2"}),
311+
("detector1", None, {"obj_name": "async_sig1", "storage_name": "async_sig1"}),
312+
("detector2", None, {"obj_name": "async_sig2", "storage_name": "async_sig2"}),
313313
]
314314

315315
plugin = BECLiveDataPlugin(mock_client)
@@ -355,12 +355,12 @@ def callback2(data, metadata):
355355
def test_subscribe_to_async_signal(self, mock_client, mock_callback):
356356
"""Test subscription to async signal."""
357357
mock_client.device_manager.get_bec_signals.return_value = [
358-
("async_sig1", None, {"device_name": "detector1"})
358+
("detector1", None, {"obj_name": "async_sig1", "storage_name": "async_sig1"})
359359
]
360360

361361
# Setup scan item
362362
scan_item = ScanItem(
363-
queue_id="test_queue", scan_number=[1], scan_id=["test_scan_id"], status="open"
363+
queue_id="test_queue", scan_number=1, scan_id="test_scan_id", status="open"
364364
)
365365
scan_item.status_message = messages.ScanStatusMessage(
366366
scan_id="test_scan_id", status="open", info={}
@@ -391,7 +391,7 @@ def test_subscribe_to_async_signal(self, mock_client, mock_callback):
391391
def test_subscribe_to_async_signal_shared_subscription(self, mock_client):
392392
"""Test that multiple callbacks share the same async signal subscription."""
393393
mock_client.device_manager.get_bec_signals.return_value = [
394-
("async_sig1", None, {"device_name": "detector1"})
394+
("detector1", None, {"obj_name": "async_sig1", "storage_name": "async_sig1"})
395395
]
396396

397397
scan_item = ScanItem(
@@ -520,7 +520,7 @@ def test_data_synchronization_mixed_sources(
520520
):
521521
"""Test data synchronization between monitored and async sources."""
522522
mock_client.device_manager.get_bec_signals.return_value = [
523-
("async_sig1", None, {"device_name": "detector1"})
523+
("detector1", None, {"obj_name": "async_sig1", "storage_name": "async_sig1"})
524524
]
525525

526526
plugin = BECLiveDataPlugin(mock_client)
@@ -588,7 +588,7 @@ def test_unsubscribe_monitored_device(
588588
def test_unsubscribe_async_signal(self, mock_client, mock_callback):
589589
"""Test unsubscribing from async signal."""
590590
mock_client.device_manager.get_bec_signals.return_value = [
591-
("async_sig1", None, {"device_name": "detector1"})
591+
("detector1", None, {"obj_name": "async_sig1", "storage_name": "async_sig1"})
592592
]
593593

594594
scan_item = ScanItem(
@@ -653,7 +653,7 @@ def test_can_provide_monitored(self, mock_client, scan_item_with_monitored_devic
653653
def test_can_provide_async_signal(self, mock_client):
654654
"""Test can_provide returns True for async signals."""
655655
mock_client.device_manager.get_bec_signals.return_value = [
656-
("async_sig1", None, {"device_name": "detector1"})
656+
("detector1", None, {"obj_name": "async_sig1", "storage_name": "async_sig1"})
657657
]
658658

659659
scan_item = ScanItem(

0 commit comments

Comments
 (0)