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

class scan.commands.comment.Comment(text='This is an example comment.')
Parameters

text – Comment Text.

Example:
>>> cmd = Comment("Scan Start.") 
genXML()
Returns

XML representation of the command.

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?

  • readbackFalse 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

readbackFalse 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?

  • readbackFalse 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.

_images/scan_alternate.png

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

readbackFalse 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

class scan.commands.delay.Delay(seconds, errhandler=None)

Delay for a fixed amount of time

Parameters

seconds – Time to delay in seconds.

Example:
>>> cmd = Delay(2.5)
genXML()
Returns

XML representation of the command.

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

class scan.commands.configlog.ConfigLog(auto, errhandler=None)

Config automatic logging.

Parameters

autoTrue to log all write access, False to only log via Log() command.

Example:
>>> cmd = ConfigLog(True)
genXML()
Returns

XML representation of the command.

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