Skip to content

Commit f752763

Browse files
committed
Replace scripting-plans docs
1 parent 167bf50 commit f752763

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

docs/tutorials/scripting-plans.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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

Comments
 (0)