|
| 1 | +# Getting started |
| 2 | + |
| 3 | +`ibex_bluesky_core` is a library which bridges the |
| 4 | +[IBEX control system](https://github.com/ISISComputingGroup/ibex_user_manual/wiki/What-Is-IBEX) |
| 5 | +and the [bluesky data acquisition framework](https://blueskyproject.io/). |
| 6 | + |
| 7 | +Bluesky is a highly flexible data acquisition system, which has previously been used at |
| 8 | +large-scale research facilities such as [NSLS-II](https://www.bnl.gov/nsls2/) and |
| 9 | +[Diamond](https://www.diamond.ac.uk/Home.html), among others. |
| 10 | + |
| 11 | +While the bluesky framework itself is generic enough to cope with many forms of data acquisition, |
| 12 | +one of the core use cases is "scanning" - that is, measuring how some experimental parameter(s) |
| 13 | +vary with respect to other parameter(s). Bluesky has extensive mechanisms and helpers for typical |
| 14 | +scanning workflows. |
| 15 | + |
| 16 | +The most important concepts in bluesky are: |
| 17 | +- **Plans** tell bluesky what to do next, in the form of **Messages** |
| 18 | +- **Devices** encapsulate the details of how some specific device is controlled |
| 19 | +- The **RunEngine** executes plans (possibly interacting with devices) |
| 20 | +- **Callbacks** do something with data emitted by the scan |
| 21 | + |
| 22 | +## Plans |
| 23 | + |
| 24 | +A plan is an _iterable_ of _messages_. A very simple plan, which doesn't do anything, is: |
| 25 | + |
| 26 | +```python |
| 27 | +from bluesky.utils import Msg |
| 28 | + |
| 29 | +my_plan = [Msg("null")] |
| 30 | +``` |
| 31 | + |
| 32 | +Where `Msg("null")` is an instruction to bluesky (which in this case, does nothing). |
| 33 | + |
| 34 | +While it's possible to write bluesky plans as any iterable, in practice plans are usually written |
| 35 | +using python [generators](https://peps.python.org/pep-0255/), using python's |
| 36 | +[`yield from`](https://peps.python.org/pep-0380/) syntax to delegate to other plans as necessary: |
| 37 | + |
| 38 | +```python |
| 39 | +import bluesky.plan_stubs as bps |
| 40 | + |
| 41 | +def plan(): |
| 42 | + yield from bps.null() |
| 43 | +``` |
| 44 | + |
| 45 | +## Devices |
| 46 | + |
| 47 | +`ibex_bluesky_core` provides built-in support for a number of ISIS-specific devices. For example, |
| 48 | +blocks are available as devices: |
| 49 | + |
| 50 | +```python |
| 51 | +from ibex_bluesky_core.devices.block import block_r, block_mot |
| 52 | + |
| 53 | +mot = block_mot("mot") # An IBEX block pointing at a motor |
| 54 | +det = block_r(float, "p5") # A readback block with float datatype |
| 55 | +``` |
| 56 | + |
| 57 | +Block objects provide several mechanisms for configuring write behaviour - see |
| 58 | +{py:obj}`ibex_bluesky_core.devices.block.BlockWriteConfig` for detailed options. |
| 59 | + |
| 60 | +Likewise, the DAE is available as a bluesky device: see [the DAE Documentation](../devices/dae.md) |
| 61 | +for full examples including example configurations. |
| 62 | + |
| 63 | +## Setting and reading values |
| 64 | + |
| 65 | +Bluesky provides plan stubs for setting & reading values from bluesky devices: `bps.mv()` and |
| 66 | +`bps.rd()` respectively. |
| 67 | + |
| 68 | +```python |
| 69 | +from ibex_bluesky_core.devices.block import BlockMot |
| 70 | +import bluesky.plan_stubs as bps |
| 71 | + |
| 72 | +def multiply_motor_pos_by_2(mot: BlockMot): |
| 73 | + current_value = yield from bps.rd(mot) |
| 74 | + yield from bps.mv(mot, current_value * 2.0) |
| 75 | +``` |
| 76 | + |
| 77 | +```{danger} |
| 78 | +Notice that we are using `bps.rd()` and `bps.mv()` here, rather than `g.cget()` or `g.cset()`. |
| 79 | +Bare `genie` or `inst` commands **must not** be used in bluesky plans - instead, prefer to use the |
| 80 | +bluesky-native functionality - i.e. plans using `yield from`. |
| 81 | +
|
| 82 | +Carefully review [calling external code](../plan_stubs/external_code.md) if you do need to call |
| 83 | +external code in a plan. |
| 84 | +``` |
| 85 | + |
| 86 | +For more details about plan stubs (plan fragments like `mv` and `read`), see |
| 87 | +[bluesky plan stubs documentation](https://blueskyproject.io/bluesky/main/plans.html#stub-plans) |
| 88 | + |
| 89 | +## Scanning |
| 90 | + |
| 91 | +Having created some simple devices, those devices can be used in standard bluesky plans: |
| 92 | + |
| 93 | +```python |
| 94 | +from ophyd_async.plan_stubs import ensure_connected |
| 95 | +import bluesky.plans as bp |
| 96 | +from ibex_bluesky_core.devices.block import block_r, block_mot |
| 97 | + |
| 98 | +def my_plan(det_block_name: str, mot_block_name: str, start: float, stop: float, num: int): |
| 99 | + mot = block_mot(mot_block_name) |
| 100 | + det = block_r(float, det_block_name) |
| 101 | + |
| 102 | + # Devices connect up-front - this means that plans are generally "fail-fast", and |
| 103 | + # will detect problems such as typos in block names before the whole plan runs. |
| 104 | + yield from ensure_connected(det, mot, force_reconnect=True) |
| 105 | + |
| 106 | + # Delegate to bluesky's scan plan. |
| 107 | + yield from bp.scan([det], mot, start, stop, num) |
| 108 | +``` |
| 109 | + |
| 110 | +For details about plans which are available directly from `bluesky` - like `bp.scan` above - see |
| 111 | +[bluesky's plan documentation](https://blueskyproject.io/bluesky/main/plans.html#pre-assembled-plans). |
| 112 | + |
| 113 | +## The `RunEngine` |
| 114 | + |
| 115 | +The `RunEngine` is the central "conductor" in bluesky - it is responsible for reading a plan and |
| 116 | +performing the associated actions on the hardware. To get a run engine instance, use: |
| 117 | + |
| 118 | +```python |
| 119 | +from ibex_bluesky_core.run_engine import get_run_engine |
| 120 | +RE = get_run_engine() |
| 121 | +``` |
| 122 | + |
| 123 | +```{tip} |
| 124 | +In the IBEX GUI, manually getting a runengine is unnecessary - it is done automatically. |
| 125 | +``` |
| 126 | + |
| 127 | +Then execute a plan using the RunEngine: |
| 128 | + |
| 129 | +``` |
| 130 | +RE(my_plan("det", "mot", 0, 10, 5)) |
| 131 | +``` |
| 132 | + |
| 133 | +Noth that typing `my_plan("det", "mot", 0, 10, 5)` does not do anything by itself. |
| 134 | +That is because `my_plan` is a python generator - which does nothing until iterated. |
| 135 | +To actually execute the plan, it must be passed to the `RunEngine`, which is conventionally |
| 136 | +called `RE`. |
| 137 | + |
| 138 | +For more detail about the RunEngine, see: |
| 139 | +- [bluesky RunEngine docs](https://blueskyproject.io/bluesky/main/tutorial.html#the-runengine) |
| 140 | +- [bluesky RunEngine API docs](https://blueskyproject.io/bluesky/main/run_engine_api.html) |
| 141 | + |
| 142 | +## Callbacks |
| 143 | + |
| 144 | +Callbacks are bluesky's mechanism for listening to data from a scan. Some examples of common callbacks |
| 145 | +are: |
| 146 | +- [File writing](../callbacks/file_writing.md) |
| 147 | +- [Plotting](../callbacks/plotting.md) |
| 148 | +- [Fitting](../fitting/fitting.md) |
| 149 | +- [Live Tables](https://blueskyproject.io/bluesky/main/callbacks.html#livetable) |
| 150 | + |
| 151 | +It is possible to use callbacks manually, when executing a plan: |
| 152 | + |
| 153 | +```python |
| 154 | +from bluesky.callbacks import LiveTable |
| 155 | + |
| 156 | +RE(my_plan("det", "mot", 0, 10, 5), LiveTable(["mot", "det"])) |
| 157 | +``` |
| 158 | + |
| 159 | +However, to save typing out callbacks repeatedly, user-specified plans can add callbacks via |
| 160 | +[`subs_decorator`](https://blueskyproject.io/bluesky/main/callbacks.html#through-a-plan): |
| 161 | + |
| 162 | +```python |
| 163 | +from ibex_bluesky_core.devices.block import block_r, block_mot |
| 164 | +from ophyd_async.plan_stubs import ensure_connected |
| 165 | +from bluesky.preprocessors import subs_decorator |
| 166 | +from bluesky.callbacks import LiveTable |
| 167 | +import bluesky.plans as bp |
| 168 | + |
| 169 | +def my_plan(det_block_name: str, mot_block_name: str, start: float, stop: float, num: int): |
| 170 | + mot = block_mot(mot_block_name) |
| 171 | + det = block_r(float, det_block_name) |
| 172 | + |
| 173 | + @subs_decorator([ |
| 174 | + LiveTable([mot.name, det.name]), |
| 175 | + ]) |
| 176 | + def _inner(): |
| 177 | + yield from ensure_connected(det, mot, force_reconnect=True) |
| 178 | + yield from bp.scan([det], mot, start, stop, num) |
| 179 | + yield from _inner() |
| 180 | +``` |
| 181 | + |
| 182 | +The above will show a `LiveTable` by default, any time `my_plan` is executed. The same mechanism can |
| 183 | +be used for example to always configure a particular scan with plots and a fit with a specific type. |
| 184 | + |
| 185 | +For more information on callbacks, see |
| 186 | +[bluesky callbacks documentation](https://blueskyproject.io/bluesky/main/callbacks.html). |
| 187 | + |
| 188 | +## See also |
| 189 | + |
| 190 | +**Plans & plan-stubs** |
| 191 | +- Bluesky [experiment plans](https://blueskyproject.io/bluesky/main/plans.html#summary) |
| 192 | +- Bluesky [plan stubs](https://blueskyproject.io/bluesky/main/plans.html#stub-plans) |
| 193 | +- {py:obj}`ibex_bluesky_core.plan_stubs` |
| 194 | + |
| 195 | +**Callbacks** |
| 196 | +- [Bluesky callbacks](https://blueskyproject.io/bluesky/main/callbacks.html) |
| 197 | +- {py:obj}`ibex_bluesky_core.callbacks` |
| 198 | +- [Fitting callbacks](../fitting/fitting.md) |
| 199 | + |
| 200 | +**Full Examples** |
| 201 | +- [Manual system tests](https://github.com/ISISComputingGroup/ibex_bluesky_core/tree/main/manual_system_tests) (full, |
| 202 | +runnable example plans) |
| 203 | + |
| 204 | +**External documentation** |
| 205 | +- [bluesky](https://blueskyproject.io/bluesky) |
| 206 | +- [ophyd-async](https://blueskyproject.io/ophyd-async) |
0 commit comments