|
| 1 | +""" |
| 2 | +Device RPC implementation. |
| 3 | +
|
| 4 | +Scan procedure: |
| 5 | + - prepare_scan |
| 6 | + - open_scan |
| 7 | + - stage |
| 8 | + - pre_scan |
| 9 | + - scan_core |
| 10 | + - at_each_point (optionally called by scan_core) |
| 11 | + - post_scan |
| 12 | + - unstage |
| 13 | + - close_scan |
| 14 | + - on_exception (called if any exception is raised during the scan) |
| 15 | +""" |
| 16 | + |
| 17 | +from __future__ import annotations |
| 18 | + |
| 19 | +from typing import Annotated |
| 20 | + |
| 21 | +from bec_lib.device import DeviceBase |
| 22 | +from bec_lib.logger import bec_logger |
| 23 | +from bec_lib.scan_args import ScanArgument |
| 24 | +from bec_server.scan_server.scans.scan_base import ScanBase |
| 25 | +from bec_server.scan_server.scans.scan_modifier import scan_hook |
| 26 | + |
| 27 | +logger = bec_logger.logger |
| 28 | + |
| 29 | + |
| 30 | +class DeviceRpc(ScanBase): |
| 31 | + # Scan Type: Hardware triggered or software triggered? |
| 32 | + # If the main trigger and readout logic is done within the at_each_point method in scan_core, choose SOFTWARE_TRIGGERED. |
| 33 | + # If the main trigger and readout logic is implemented on a device that is simply kicked off in this scan, choose HARDWARE_TRIGGERED. |
| 34 | + # This primarily serves as information for devices: The device may need to react differently if a software trigger is expected |
| 35 | + # for every point. |
| 36 | + scan_type = None |
| 37 | + is_scan = False |
| 38 | + |
| 39 | + # Scan name: This is the name of the scan, e.g. "line_scan". This is used for display purposes and to identify the scan type in user interfaces. |
| 40 | + # Choose a descriptive name that does not conflict with existing scan names. |
| 41 | + # It must be a valid Python identifier, that is, it can only contain letters, numbers, and underscores, and must not start with a number. |
| 42 | + scan_name = "_v4_device_rpc" |
| 43 | + |
| 44 | + def __init__( |
| 45 | + self, |
| 46 | + device: Annotated[DeviceBase, ScanArgument(display_name="Device", description="Device.")], |
| 47 | + func: Annotated[ |
| 48 | + str, ScanArgument(display_name="RPC function", description="RPC function.") |
| 49 | + ], |
| 50 | + func_args: Annotated[ |
| 51 | + list, |
| 52 | + ScanArgument( |
| 53 | + display_name="RPC function arguments", description="RPC function arguments." |
| 54 | + ), |
| 55 | + ], |
| 56 | + func_kwargs: Annotated[ |
| 57 | + dict, |
| 58 | + ScanArgument( |
| 59 | + display_name="RPC function keyword arguments", |
| 60 | + description="RPC function keyword arguments.", |
| 61 | + ), |
| 62 | + ], |
| 63 | + rpc_id: Annotated[str, ScanArgument(display_name="RPC ID", description="RPC ID.")], |
| 64 | + **kwargs, |
| 65 | + ): |
| 66 | + """ |
| 67 | + Scan implementation. |
| 68 | +
|
| 69 | + Args: |
| 70 | + device (DeviceBase): Device. |
| 71 | + func (str): RPC function. |
| 72 | + func_args (list): RPC function arguments. |
| 73 | + func_kwargs (dict): RPC function keyword arguments. |
| 74 | +
|
| 75 | + Returns: |
| 76 | + ScanReport |
| 77 | + """ |
| 78 | + super().__init__(**kwargs) |
| 79 | + self.device = device |
| 80 | + self.rpc_id = rpc_id |
| 81 | + self.func = func |
| 82 | + self.func_args = func_args |
| 83 | + self.func_kwargs = func_kwargs |
| 84 | + |
| 85 | + @scan_hook |
| 86 | + def prepare_scan(self): |
| 87 | + """ |
| 88 | + Prepare the scan. This can include any steps that need to be executed |
| 89 | + before the scan is opened, such as preparing the positions (if not done already) |
| 90 | + or setting up the devices. |
| 91 | + """ |
| 92 | + |
| 93 | + @scan_hook |
| 94 | + def open_scan(self): |
| 95 | + """ |
| 96 | + Open the scan. |
| 97 | + This step must call self.actions.open_scan() to ensure that a new scan is |
| 98 | + opened. Make sure to prepare the scan metadata before, either in |
| 99 | + prepare_scan() or in open_scan() itself and call self.update_scan_info(...) |
| 100 | + to update the scan metadata if needed. |
| 101 | + """ |
| 102 | + |
| 103 | + @scan_hook |
| 104 | + def stage(self): |
| 105 | + """ |
| 106 | + Stage the devices for the upcoming scan. The stage logic is typically |
| 107 | + implemented on the device itself (i.e. by the device's stage method). |
| 108 | + However, if there are any additional steps that need to be executed before |
| 109 | + staging the devices, they can be implemented here. |
| 110 | + """ |
| 111 | + |
| 112 | + @scan_hook |
| 113 | + def pre_scan(self): |
| 114 | + """ |
| 115 | + Pre-scan steps to be executed before the main scan logic. |
| 116 | + This is typically the last chance to prepare the devices before the core scan |
| 117 | + logic is executed. For example, this is a good place to initialize time-criticial |
| 118 | + devices, e.g. devices that have a short timeout. |
| 119 | + The pre-scan logic is typically implemented on the device itself. |
| 120 | + """ |
| 121 | + |
| 122 | + @scan_hook |
| 123 | + def scan_core(self): |
| 124 | + """ |
| 125 | + Core scan logic to be executed during the scan. |
| 126 | + This is where the main scan logic should be implemented. |
| 127 | + """ |
| 128 | + status = self.actions.rpc_call_no_wait( |
| 129 | + self.device, self.func, self.rpc_id, *self.func_args, **self.func_kwargs |
| 130 | + ) |
| 131 | + # We don't wait for the RPC to resolve |
| 132 | + self.actions._status_registry.pop(status._device_instr_id) |
| 133 | + |
| 134 | + @scan_hook |
| 135 | + def at_each_point(self): |
| 136 | + """ |
| 137 | + Logic to be executed at each acquisition point during the scan. |
| 138 | + """ |
| 139 | + |
| 140 | + @scan_hook |
| 141 | + def post_scan(self): |
| 142 | + """ |
| 143 | + Post-scan steps to be executed after the main scan logic. |
| 144 | + """ |
| 145 | + |
| 146 | + @scan_hook |
| 147 | + def unstage(self): |
| 148 | + """Unstage the scan by executing post-scan steps.""" |
| 149 | + |
| 150 | + @scan_hook |
| 151 | + def close_scan(self): |
| 152 | + """Close the scan.""" |
| 153 | + |
| 154 | + @scan_hook |
| 155 | + def on_exception(self, exception: Exception): |
| 156 | + """ |
| 157 | + Handle exceptions that occur during the scan. |
| 158 | + This is a good place to implement any cleanup logic that needs to be executed in case of an exception, |
| 159 | + such as returning the devices to a safe state or moving the motors back to their starting position. |
| 160 | + """ |
| 161 | + |
| 162 | + ####################################################### |
| 163 | + ######### Helper methods for the scan logic ########### |
| 164 | + ####################################################### |
| 165 | + |
| 166 | + # Implement scan-specific helper methods below. |
0 commit comments