|
| 1 | +# Scripting Plans |
| 2 | + |
| 3 | +While the CLI can be used to query devices and run plans, it can be useful to |
| 4 | +combine multiple plans within a better interface than bash/shell scripting. |
| 5 | + |
| 6 | +For this, `blueapi` can be used as a library providing a `BlueapiClient` |
| 7 | +wrapping interactions with the server. |
| 8 | + |
| 9 | +## Login to blueapi |
| 10 | + |
| 11 | +The following steps require the user to have logged in blueapi. This can be done |
| 12 | +via the `blueapi login` command. |
| 13 | + |
| 14 | +## Create an instance of the client |
| 15 | + |
| 16 | +```python |
| 17 | +from blueapi.client.client import BlueapiClient |
| 18 | + |
| 19 | +# A client can be created from either a config instance or the path to a config |
| 20 | +# file. The minimal configuration required # is: |
| 21 | +# api: |
| 22 | +# url: https://address.of.blueapi:1234 |
| 23 | +# stomp: |
| 24 | +# enabled: true |
| 25 | +# url: tcp://address.of.rabbitmq:61613 |
| 26 | +bc = BlueapiClient.from_config_file("/path/to/config.yaml") |
| 27 | +``` |
| 28 | + |
| 29 | +Plans and devices are available via the `plans` and `devices` attributes of the |
| 30 | +client. It can be useful to alias these locally to reduce the boilerplate in |
| 31 | +scripts. |
| 32 | + |
| 33 | +```python |
| 34 | +plans = bc.plans |
| 35 | +devices = bc.devices |
| 36 | +``` |
| 37 | + |
| 38 | +## Query devices |
| 39 | + |
| 40 | +The devices available on the server are accessible via the `devices` attribute |
| 41 | +of the client. |
| 42 | + |
| 43 | +```python |
| 44 | +for device in bc.devices: |
| 45 | + print(device) |
| 46 | +``` |
| 47 | + |
| 48 | +Individual devices can be accessed as attributes on the `devices` field. It can |
| 49 | +also be useful to alias these locally. |
| 50 | + |
| 51 | +```python |
| 52 | +det = bc.devices.det |
| 53 | +stage = bc.devices.stage |
| 54 | +``` |
| 55 | + |
| 56 | +Child devices can be accessed via their parent devices |
| 57 | + |
| 58 | +```python |
| 59 | +stage_x = stage.x |
| 60 | +``` |
| 61 | + |
| 62 | +Trying to access a child device that does not exist will raise an |
| 63 | +`AttributeError` |
| 64 | + |
| 65 | +## Run a plan |
| 66 | + |
| 67 | +Plans are accessible via the `plans` attribute of the client instance. They can, |
| 68 | +for the most part, be treated as if they were local functions. |
| 69 | + |
| 70 | +```python |
| 71 | +bc.plans.count([bc.devices.det], num=3, delay=4.2) |
| 72 | +``` |
| 73 | + |
| 74 | +Running a plan in this way will block until the plan is complete. If the script |
| 75 | +is interrupted (eg via Ctrl-C) while a plan is running it will be aborted before |
| 76 | +the script exits. |
| 77 | + |
| 78 | +Where parameters to a plan are optional, they can be omitted from the method |
| 79 | +call. Where parameters are required, they can be passed either as positional or |
| 80 | +named arguments. |
| 81 | + |
| 82 | +## Run multiple plans |
| 83 | + |
| 84 | +Plans can then be co-ordinated using standard python constructs, eg to run a |
| 85 | +plan multiple times |
| 86 | + |
| 87 | +```python |
| 88 | +for temp in range(1, 5): |
| 89 | + plans.set_absolute({devices.temp: temp}) |
| 90 | + plans.count([devices.det]) |
| 91 | +``` |
| 92 | + |
| 93 | +## Passing more complex arguments |
| 94 | + |
| 95 | +Anything passed to a plan function will be serialized into JSON before being |
| 96 | +sent to the server. For many types you can pass the instance directly and the |
| 97 | +serialization should handle the conversion for you. |
| 98 | + |
| 99 | +```python |
| 100 | +from scanspec.specs import Line |
| 101 | + |
| 102 | +bc.plans.spec_scan(detectors=[det], spec=Line(bc.devices.stage.x, 0, 10, 11)) |
| 103 | +``` |
| 104 | + |
| 105 | +if a type does not serialize correctly, passing the JSON equivalent should be |
| 106 | +possible instead. For instance the above is equivalent to |
| 107 | + |
| 108 | +```python |
| 109 | +bc.plans.spec_scan(detectors=[det], spec={ |
| 110 | + "axis": "stage.x", |
| 111 | + "start": 0.0, |
| 112 | + "stop": 10.0, |
| 113 | + "num": 11, |
| 114 | + "type": "Line"}) |
| 115 | +``` |
| 116 | + |
| 117 | +## Add callbacks |
| 118 | + |
| 119 | +By default there is no indication of progress while a scan is running however it |
| 120 | +is possible to subscribe to events so that updates can be provided. |
| 121 | + |
| 122 | +A callback should accept a single parameter which will be the event from server. |
| 123 | +This will be one of `WorkerEvent`, `ProgressEvent` or `DataEvent`. |
| 124 | + |
| 125 | +An example that prints data for each point could be something like |
| 126 | + |
| 127 | +```python |
| 128 | +def feedback(evt): |
| 129 | + match evt: |
| 130 | + case DataEvent(name="start"): |
| 131 | + print("Run started") |
| 132 | + case DataEvent(name="stop", doc={"exit_status": status}): |
| 133 | + print("Run complete: ", status) |
| 134 | + case DataEvent(name="event", doc={"seq_num": point, "data": data}): |
| 135 | + print(f" Point {point}: {data}") |
| 136 | + |
| 137 | +bc.add_callback(feedback) |
| 138 | + |
| 139 | +bc.plans.spec_scan([bc.devices.det], Line(bc.devices.stage.x, 0, 1, 11)) |
| 140 | +``` |
| 141 | + |
| 142 | +The above prints the following as the scan progresses |
| 143 | + |
| 144 | +``` |
| 145 | +Run started |
| 146 | + Point 1: {'stage-x': 0.0} |
| 147 | + Point 2: {'stage-x': 0.1} |
| 148 | + Point 3: {'stage-x': 0.2} |
| 149 | + Point 4: {'stage-x': 0.3} |
| 150 | + Point 5: {'stage-x': 0.4} |
| 151 | + Point 6: {'stage-x': 0.5} |
| 152 | + Point 7: {'stage-x': 0.6} |
| 153 | + Point 8: {'stage-x': 0.7000000000000001} |
| 154 | + Point 9: {'stage-x': 0.8} |
| 155 | + Point 10: {'stage-x': 0.9} |
| 156 | + Point 11: {'stage-x': 1.0} |
| 157 | +Run complete: success |
| 158 | +``` |
| 159 | + |
| 160 | +The `add_callback` method returns an ID that can be used to remove the callback |
| 161 | + |
| 162 | +```python |
| 163 | +# Add the callback and record the handle |
| 164 | +hnd = bc.add_callback(callback_function) |
| 165 | + |
| 166 | +# remove the callback using the returned handle |
| 167 | +bc.remove_callback(hnd) |
| 168 | +``` |
0 commit comments