Scan Commands¶
Assemble a scan from commands:
from scan import *
# Assemble list of commands
cmds = [
Set('shutter', 1),
Loop('motor_x', 1, 10, 0.5,
[
Comment('daq:start'),
Delay(10),
Comment('daq:stop'),
Log('motor_x')
], readback='motor_x', tolerance=0.5)
]
print("Basic list of commands:")
print(cmds)
# Result:
# [Set('shutter', 1), Loop('motor_x', 1, 10, 0.5, [ Comment('daq:start'),...
# Alternatively, use `CommandSequence` which can start
# empty or use list of commands
seq = CommandSequence(cmds)
# Commands can be added to sequence
seq.append(Comment('Done'))
print("`CommandSequence` results in nicer printout:")
print(seq)
# Result:
# [
# Set('shutter', 1)
# Loop('motor_x', 1, 10, 0.5,
# [
# Comment('daq:start'),
# Delay(10),
# Comment('daq:stop'),
# Log('motor_x')
# ], readback='motor_x', tolerance=0.5)
# Comment('Done')
# ]
# .. and then submit to scan server for execution
client = ScanClient()
id = client.submit(seq)
Comment¶
Set¶
-
class
scan.commands.set.
Set
(device, value, completion=False, readback=False, readback_value=None, tolerance=0.0, timeout=0.0, errhandler=None)¶ Set a device to a value.
With optional check of completion and readback verification.
- Parameters
device – Device name
value – Value
completion – Await callback completion?
readback – False to not check any readback, True to wait for readback from the device, or name of specific device to check for readback.
readback_value – None to use value when checking readback, otherwise an alternate value to check.
tolerance – Tolerance when checking numeric readback.
timeout – Timeout in seconds, used for completion and readback.
errhandler – Error handler
- Example:
>>> cmd = Set('position', 10.5) >>> cmd = Set('setpoint', 10.5, completion=True, timeout=30.0, readback='other_pv', tolerance=0.20)
Note usage of timeout: When the command awaits completion, the timeout is applied to the completion check, i.e. we await the completion callback for timeout seconds. If another readback check is performed after the completion, this check is immediate, comparing the readback right now, and not waiting for the readback to match within another timeout seconds.
On the other hand, if completion is not used, the timeout is applied to the readback check. So in case a readback comparison is requested, we wait for up to timeout seconds for the readback to be within tolerance.
Note use of completion: The EPICS network protocol, be it Channel Access or PV Access, does not communicate if completion (‘put-callback’) is actually supported. When writing to a PV that does not support completion, the call is returned right away, just as it would for a PV that supports completion and happens to complete quickly. Similarly, a PV that supports completion will only tell us when it’s ‘done’, no matter if it completed successfully, or if it eventually gave up and completed without actually reaching the desired setpoint.
Use case 1: Neither completion nor readback
The Set command simply writes to the device.
This would be suitable for a write-and-forget PV. For example, a PV that turns a power supply on or off, and the device reacts quasi immediately.
Use case 2: No completion, but readback
The Set command writes to the device, and uses the timeout to wait for the readback to match the written value.
This can be sufficient for simple, well behaved devices, but can be problematic for a device where the readback will take time to settle. Examples include PID-controlled devices with overshoot and settling time, or motors with backlash compensation and retries. The readback can be close to the setpoint, but the device might not have settled. We can then erroneously consider it ‘done’ while the device is in fact still actively changing its value.
Use case 3: Enable completion but no readback
The Set command writes to the device, then uses the timeout to wait for the completion confirmation.
This can be used with PVs that support completion and are dependable. We cannot distinguish between a completion that is successful, versus completion as a result of the device giving up.
If this mode is by accident used with a PV that doesn’t actually support completion, the IOC will immediately confirm the completion, behaving just like case 1.
Use case 4: Enable completion and readback
This is the ideal case, which is for example supported by the ‘motor’ record or EPICS databases for Lakeshore controllers. The Set command writes to the device, waits for the completion (based on timeout), and then compares the written value against the readback PV to check if we completed successfully, or if the device completed without being able to actually reach the setpoint.
Note that this must only be used with devices that actually support completion. When applied to a plain PV that does not support completion, we will immediately receive the completion confirmation, then check the readback, which is very likely not matching the setpoint, yet, and fail. So while this is best for PVs that support completion, it is worst for PVs that don’t.
Unfortunately there is no way in EPICS to determine the ‘correct’ settings for a PV without knowing how it is implemented on the IOC, so the choice of completion, readback and timeout needs to be configured by somebody who knows the PV’s behavior.
The readback can be used in several ways. readback=True checks if the written PV has the written value. readback=’SomeOtherPV’ checks if some other PV, for example the ‘.RBV’ of a motor, has the written value. By adding readback_value=42 the command checks if the readback, i.e. either the written PV or an alternate readback PV, has the provided value. This is typically used to check if some ‘status’ PV indicates ‘OK’ after writing.
-
genXML
()¶ - Returns
XML representation of the command.
-
getDevice
()¶ - Returns
Device name
-
setCompletion
(completion)¶ Change completion
- Parameters
completion – Await callback completion?
-
setReadback
(readback)¶ Change readback
- Parameters
readback – False to not check any readback, True to wait for readback from the device, or name of specific device to check for readback.
-
setTimeout
(timeout)¶ Change timeout
- Parameters
timeout – Timeout in seconds, used for completion and readback.
-
setTolerance
(tolerance)¶ Change tolerance
- Parameters
tolerance – Tolerance when checking numeric readback.
Wait¶
-
class
scan.commands.wait.
Wait
(device, value, comparison='=', tolerance=0.0, timeout=0.0, errhandler=None)¶ Wait until a condition is met, i.e. a device reaches a value.
- Parameters
device – Name of PV or device.
value – Desired value.
comparison – How current value is compared to the desired value. Defaults to ‘=’. Other options: ‘!=’, ‘>’, ‘>=’, ‘<’ , ‘<=’, ‘increase by’,’decrease by’. For string data, supported options are ‘=’, ‘!=’, ‘>’, ‘>=’, ‘<’ , ‘<=’.
tolerance – Tolerance used for numeric ‘=’ comparison. Defaults to 0, not used for string values or anything but ‘=’.
timeout – Timeout in seconds. Default 0 to wait ‘forever’.
errhandler – Default None.
For ‘increase by’ and ‘decrease by’, the initial value is taken when the command starts executing, and then it awaits an increment or decrement of the value from that initial value.
- Example:
>>> cmd = Wait('shutter', 1) >>> cmd = Wait('position', 25.0, timeout=60.0, tolerance=0.5) >>> cmd = Wait('counts', 1e12, comparison='>=', timeout=10.0) >>> cmd = Wait('counts', 1e12, comparison='increase by', timeout=5.0, errhandler='someHandler')
-
genXML
()¶ - Returns
XML representation of the command.
-
getDevice
()¶ - Returns
Device name
-
setComparison
(comparison)¶ Change comparison
- Parameters
comparison – How current value is compared to the desired value. Options: ‘=’, ‘>’, ‘>=’, ‘<’ , ‘<=’, ‘increase by’,’decrease by’
-
setTimeout
(timeout)¶ Change timeout
- Parameters
timeout – Timeout in seconds, used for completion and readback.
-
setTolerance
(tolerance)¶ Change tolerance
- Parameters
tolerance – Tolerance when checking numeric readback.
Loop¶
-
class
scan.commands.loop.
Loop
(device, start, end, step, body=None, *args, **kwargs)¶ Set a device to various values in a loop.
Optional check of completion and readback verification.
- Parameters
device – Device name
start – Initial value
end – Final value
step – Step size
body – One or more commands
completion – Await callback completion?
readback – False to not check any readback, True to wait for readback from the ‘device’, or name of specific device to check for readback.
tolerance – Tolerance when checking numeric readback. Defaults to 0.
timeout – Timeout in seconds, used for completion and readback check.
errhandler – Error handler
Examples:
- Set pv1 to 1, 1.5, 2, 2.5, 3, .., 9.5, 10:
>>> cmd = Loop('pv1', 1, 10, 0.5)
- Set pv1 to 10, 9, 8, 7, .., 1, i.e. stepping down:
>>> cmd = Loop('pv1', 10, 1, -1)
- At each step of the loop, perform additional commands:
>>> cmd = Loop('pv1', 1, 10, 1, Set('daq', 1), Delay(10), Set('daq', 0)) >>> cmd = Loop('pv1', 1, 10, 1, ... body = [ Set('daq', 1), Delay(10), Set('daq', 0) ])
- When after loop updates pv1, check for its readback to match, then perform commands within the loop:
>>> cmd = Loop('pv1', 1, 10, 1, Set('daq', 1), Delay(10), Set('daq', 0), readback=True)
For nested loops, note the special handling of the step direction. Consider a normal nested loop for ‘xpos’ and ‘ypos’ both stepping from 0 to 5 with a positive step:
>>> cmd = Loop('xpos', 0, 5, 1, [ Loop('ypos', 0, 5, 1 ])
In this example, the step size for the inner loop is ‘wrong’. Going from 0 to 5 ordinarily means stepping up by +1 in each loop iteration, but the step is instead provided as -1, as if this was a loop from 5 down to 0:
>>> cmd = Loop('xpos', 0, 5, 1, [ Loop('ypos', 0, 5, -1 ])
As a result, the loop will cycle its direction between +1 and -1.
Note how the direction of the inner loop changes. This can be useful for scanning the X/Y surface of a sample.
-
format
(level=0)¶ Format the command, possible over multiple lines.
- Parameters
level – Indentation level
- Returns
Human-readable, possibly multi-line representation.
-
genXML
()¶ - Returns
XML representation of the command.
-
getBody
()¶ Obtain list of body commands.
The Loop(..) constructor creates a safe copy of the passed ‘body’ to prevent side effects when that body is later changed and maybe used to construct another Loop(..) instance.
If there is a desire to change the loop’s body (before it’s submitted to the scan server), this method provides that list of commands. :return: Loop body
-
getDevice
()¶ - Returns
Device name
-
setCompletion
(completion)¶ Change completion
- Parameters
completion – Await callback completion?
-
setReadback
(readback)¶ Change readback
- Parameters
readback – False to not check any readback, True to wait for readback from the device, or name of specific device to check for readback.
-
setTimeout
(timeout)¶ Change timeout
- Parameters
timeout – Timeout in seconds, used for completion and readback.
-
setTolerance
(tolerance)¶ Change tolerance
- Parameters
tolerance – Tolerance when checking numeric readback.
Delay¶
Log¶
-
class
scan.commands.log.
Log
(devices=None, *args, **kwargs)¶ Log current value of one or more devices.
- Parameters
devices – One or more devices to log
The current value of listed devices is logged with the scan and can later be retrieved.
The logged data is meant to allow tracking the progress of the scan or to debug details of a scan. It does not provide data acquisition.
- Examples:
>>> cmd = Log() >>> cmd = Log("pv1") >>> cmd = Log("pv1", "pv2") >>> cmd = Log(devices=["pv1", "pv2"]) >>> cmd = Log(devices=["pv1", "pv2"], errhandler="OnErrorContinue")
-
genXML
()¶ - Returns
XML representation of the command.
Sequence¶
-
class
scan.commands.sequence.
Sequence
(body=None, *args, **kwargs)¶ Perform sequence of commands.
Can be used to assemble ‘meta commands’ which consist of several basic commands.
- Parameters
body – Commands or list of commands
errhandler – Optional error handler.
Examples:
- Do nothing::
>>> cmd = Sequence()
- Perform one command, same as directly using Set(‘x’, 1):
>>> cmd = Sequence(Set('x', 1))
- Set two PVs to a value:
>>> cmd = Sequence( Set('x', 1), Set('y', 2) )
Becomes more useful in combination with
Parallel
.- This example performs two sequences in parallel:
>>> Parallel( Sequence(Set('x', 1), Wait('x_loc', 10) ), >>> Sequence(Set('y', 2), Wait('y_loc', 20) ) )
- Nested Sequences are flattened:
>>> # Results in the same sequence >>> cmd = Sequence( Sequence(Comment("One"), Comment("Two")), Comment("Three")) >>> cmd = Sequence( Comment("One"), Comment("Two"), Comment("Three"))
-
format
(level=0)¶ Format the command, possible over multiple lines.
- Parameters
level – Indentation level
- Returns
Human-readable, possibly multi-line representation.
-
genXML
()¶ - Returns
XML representation of the command.
Parallel¶
-
class
scan.commands.parallel.
Parallel
(body=None, *args, **kwargs)¶ Perform multiple commands in parallel.
Each of the commands performed in parallel may await callback completion and/or check readbacks.
The Parallel command completes when all of the commands in its body have finished executing, or an optional timeout expires.
- Parameters
body – Commands or list of commands
timeout – Optional timeout in seconds. By default, wait forever.
errhandler – Optional error handler.
Examples:
- Do nothing:
>>> cmd = Parallel()
- Perform one command, same as directly using Set(‘x’, 1):
>>> cmd = Parallel(Set('x', 1))
- Set two PVs to a value, each awaiting callback completion:
>>> cmd = Parallel(Set('x', 1, completion=True), ... Set('y', 2, completion=True))
- Given a list of commands, perform them all in parallel:
>>> cmd = Parallel(body=[command1, command2, command3])
-
format
(level=0)¶ Format the command, possible over multiple lines.
- Parameters
level – Indentation level
- Returns
Human-readable, possibly multi-line representation.
-
genXML
()¶ - Returns
XML representation of the command.
If¶
-
class
scan.commands.iff.
If
(device, comparison, value, body=None, *args, **kwargs)¶ Conditionally execute commands.
- Parameters
device – Device name
value – Desired value.
comparison – How current value is compared to the desired value. Options: ‘=’, ‘!=’, ‘>’, ‘>=’, ‘<’ , ‘<=’.
body – One or more commands
tolerance – Tolerance when checking numeric readback. Defaults to 0.1
errhandler – Error handler
Examples:
>>> cmd = If('pv1', '=', 10, [ Comment("It's 10") ])
-
format
(level=0)¶ Format the command, possible over multiple lines.
- Parameters
level – Indentation level
- Returns
Human-readable, possibly multi-line representation.
-
genXML
()¶ - Returns
XML representation of the command.
-
getBody
()¶ Obtain list of body commands.
The If(..) constructor creates a safe copy of the passed ‘body’ to prevent side effects when that body is later changed and maybe used to construct another If(..) instance.
If there is a desire to change the body (before it’s submitted to the scan server), this method provides that list of commands.
- Returns
Body
-
getDevice
()¶ - Returns
Device name
Include¶
-
class
scan.commands.include.
Include
(scan, macros=None, errhandler=None)¶ Include another scan.
Allows re-use of existing scans within a larger scan. The included scan can use “$(macro)” for device names.
- Parameters
scan – Name of scan file. Must contain valid scan in XML format and be on the server’s list of script_paths.
macros – “name=value, other=42”
- Example::
>>> cmd = Include('PrepMotor.scn', macros='motor=MyMotor1')
-
genXML
()¶ - Returns
XML representation of the command.
ConfigLog¶
Script¶
-
class
scan.commands.script.
Script
(script='the_script.py', *args, **kwargs)¶ Custom command implemented in Jython
This command executes Jython code.
- Parameters
script – Name of the script class.
arguments... – Arguments to the script.
Example Script:
Scripts must derive from ScanScript:
>>> class MyScript(ScanScript): ... # Script command that gets logged data for a PV, ... # writes the sum to a '.._sum' PV ... def __init__(self, name): ... self.name = name ... ... def getDeviceNames(self): ... # PVs that this command needs ... return [ self.name + "_sum" ] ... ... def run(self, context, args): ... [ x ] = context.getData(self.name) ... s = 0 ... for v in x: ... s += v ... context.write(self.name + "_sum", s)
For details refer to the Javadoc of the Scan Server.
A Jython script that defines the class MyScript must be stored in file named myscript.py, i.e. using the lower case version of the class name. It must be located on the scan server script path.
- Example Script commands:
>>> cmd = Script("MyScript", "pos", 42.3)
-
genXML
()¶ - Returns
XML representation of the command.
Command Sequence¶
-
class
scan.commands.commandsequence.
CommandSequence
(*commands)¶ A sequence of scan commands
Basically a list of commands, with helper methods to pretty-print and convert to the raw XML required by the scan server.
- Parameters
commands – One or more commands, or existing list of commands.
-
append
(*commands)¶ Append more commands to the sequence
- Parameters
commands – One or more commands, or existing list of commands.
-
format
()¶ Format for printing
- Example:
>>> cmds = CommandSequence(Comment('Example'), Loop('pos', 1, 5, 0.5, Set('run', 1), Delay(2), Set('run', 0))) >>> print cmds.toSeqString()
Output:
[ Comment('Example'), Loop('pos', 1, 5, 0.5, [ Set('run', 1), Delay(2), Set('run', 0) ]) ]
-
genSCN
()¶ - Returns
Command in XML format suitable for scan server