def testParallel(self):
        print("\n=== Parallel without Wait ===")
        table_scan = TableScan(("+p A", "+p B", "C", "+p D", "+p E", "F"), [
            ["1", "2", "3", "4", "5", "6"],
        ])
        cmds = handle(table_scan)
        self.assertEqual(
            str(cmds),
            "[Parallel(Set('A', 1.0), Set('B', 2.0)), Set('C', 3.0), Parallel(Set('D', 4.0), Set('E', 5.0)), Set('F', 6.0)]"
        )

        print("\n=== Parallel with Wait For ===")
        table_scan = TableScan(
            ("+p A", "+p B", "C", "+p D", "+p E", "Wait For", "Value"), [
                ["1", "2", "3", "4", "5", "completion", "10"],
                ["6", "7", "8", "9", "10", "Seconds", "10"],
            ],
            start=Comment('Start Run'),
            stop=Comment('Stop Run'))
        cmds = handle(table_scan)
        self.assertEqual(
            str(cmds),
            "[Parallel(Set('A', 1.0), Set('B', 2.0)), Set('C', 3.0), Comment('Start Run'), Parallel(Set('D', 4.0), Set('E', 5.0)), Log('A', 'B', 'C', 'D', 'E'), Comment('Stop Run'), Parallel(Set('A', 6.0), Set('B', 7.0)), Set('C', 8.0), Parallel(Set('D', 9.0), Set('E', 10.0)), Comment('Start Run'), Delay(10), Log('A', 'B', 'C', 'D', 'E'), Comment('Stop Run')]"
        )

        print("\n=== Parallel with Delay and Wait For ===")
        table_scan = TableScan(
            ("+p A", "+p B", "Delay", "Wait For", "Value"), [
                ["1", "2", "00:05:00", "counts", "10"],
            ],
            start=Comment('Start Run'),
            stop=Comment('Stop Run'),
            special={'Delay': lambda cell: Delay(parseSeconds(cell))})
        cmds = handle(table_scan)
        self.assertEqual(
            str(cmds),
            "[Parallel(Set('A', 1.0), Set('B', 2.0)), Delay(300), Comment('Start Run'), Wait('counts', 10.0, comparison='>=', tolerance=0.1), Log('A', 'B', 'counts'), Comment('Stop Run')]"
        )

        print("\n=== Parallel with range and Delay ===")
        table_scan = TableScan(
            ("+p A", "+p B", "Delay"), [
                ["1", "[2, 4, 6]", "00:05:00"],
            ],
            special={'Delay': lambda cell: Delay(parseSeconds(cell))})
        cmds = handle(table_scan)
        self.assertEqual(
            str(cmds),
            "[Parallel(Set('A', 1.0), Set('B', 2.0)), Delay(300), Parallel(Set('A', 1.0), Set('B', 4.0)), Delay(300), Parallel(Set('A', 1.0), Set('B', 6.0)), Delay(300)]"
        )

        print("\n=== Parallel with Timeout ===")
        table_scan = TableScan(("+p A", "Wait For", "Value", "Or Time"), [
            ["1", "Completion", "", "00:05:00"],
        ])
        cmds = handle(table_scan)
        self.assertEqual(
            str(cmds),
            "[Parallel(Set('A', 1.0), timeout=300, errhandler='OnErrorContinue'), Log('A')]"
        )
Exemple #2
0
    def createScan(self, lineinfo=True):
        """Create scan.

        :param lineinfo: By default Comment commands are added for line info.
                         If scan settings include a "table_scan_row",
                         that PV will also be set.
        :return: List of commands.
        """
        # Parse column headers.
        settings = getScanSettings()
        col_device = [None for i in range(self.cols)]
        i = 0
        while i < self.cols:
            if self.headers[i] == TableScan.WAITFOR:
                # Column TableScan.WAITFOR must be followed by TableScan.VALUE
                if i >= self.cols - 1 or self.headers[i +
                                                      1] != TableScan.VALUE:
                    raise ValueError(TableScan.WAITFOR +
                                     " column must be followed by " +
                                     TableScan.VALUE)
                # .. and may then be followed by TableScan.OR_TIME
                if i < self.cols - 2 and self.headers[i +
                                                      2] == TableScan.OR_TIME:
                    i += 2
                else:
                    i += 1
            elif self.headers[i] == TableScan.COMMENT:
                # Comment is no device name
                pass
            else:
                # Parse device info
                col_device[i] = settings.parseDeviceSettings(self.headers[i])
            i += 1

        # Add first column of line numbers
        numbered = []
        for row in self.rows:
            numbered_row = list(row)
            numbered_row.insert(0, len(numbered) + 1)
            numbered.append(numbered_row)
        # Expand any range(start, end, step) cells
        # (which will duplicate the line numbers)
        expanded_rows = expandRanges(numbered)

        # Assemble commands for each row in the table
        current_line = 0
        commands = list()
        log_devices = list()
        if self.log_always is not None:
            log_devices = list(self.log_always)
        if self.pre:
            commands += self.pre
        for numbered_row in expanded_rows:
            line = numbered_row[0]
            row = numbered_row[1:]
            row_commands = commands
            if line != current_line:
                if lineinfo:
                    row_commands.append(Comment("# Line %d" % line))
                    if settings.table_scan_row:
                        row_commands.append(
                            SettingsBasedSet(settings.table_scan_row, line))
                current_line = line
            # Parallel commands to execute in this row
            self.parallel_commands = list()
            # Handle all columns
            c = 0
            while c < self.cols:
                what = self.headers[c]
                if len(row[c]) <= 0:
                    pass  # Empty column, nothing to do
                elif what in self.special:
                    self.__flushParallel(row_commands)
                    special_handler = self.special[what]
                    value = self.__getValue(row[c])
                    command = special_handler(value)
                    row_commands.append(command)
                elif what == TableScan.COMMENT:
                    self.__flushParallel(row_commands)
                    text = row[c]
                    row_commands.append(Comment(text))
                    # TODO if self.settings.comment:
                    #       row_commands.append(SetCommand(self.settings.comment, text))
                elif what == TableScan.WAITFOR:
                    waitfor = row[c]
                    value = self.__getValue(row[c + 1])

                    if waitfor.lower() != TableScan.COMPLETION:
                        # Complete accumulated parallel_commands before starting the run
                        self.__flushParallel(row_commands)

                    # Optional commands to mark start of a "Wait For"
                    if self.start:
                        row_commands += self.start

                    if waitfor.lower() == TableScan.COMPLETION:
                        # Assert that there are any parallel commands,
                        # because otherwise the 'WaitFor - Completion' was likely an error
                        if not self.__flushParallel(row_commands):
                            raise Exception(
                                "Line %d has no parallel commands to complete"
                                % line)
                    elif waitfor.lower() in (TableScan.SECONDS,
                                             TableScan.TIME):
                        if value:
                            row_commands.append(Delay(parseSeconds(value)))
                    else:
                        timeout = None
                        errhandler = None
                        if c + 2 < self.cols and self.headers[
                                c + 2] == TableScan.OR_TIME:
                            or_time = row[c + 2].strip()
                            if len(or_time) > 0:
                                timeout = parseSeconds(or_time)
                                errhandler = "OnErrorContinue"
                        cmd = SettingsBasedWait(waitfor,
                                                value,
                                                timeout=timeout,
                                                errhandler=errhandler)
                        row_commands.append(cmd)
                        if not waitfor in log_devices:
                            log_devices.append(waitfor)

                    if len(log_devices) > 0:
                        row_commands.append(Log(log_devices))

                    # Optional commands to mark end of a "Wait For"
                    if self.stop:
                        row_commands += self.stop

                    # Skip TableScan.VALUE in addition to current column,
                    # so next two Exceptions should not happen unless there's an empty "WAIT_FOR"
                    if c + 2 < self.cols and self.headers[
                            c + 2] == TableScan.OR_TIME:
                        c = c + 2
                    else:
                        c = c + 1
                elif what == TableScan.VALUE:
                    raise Exception(
                        "Line %d: Found value '%s' in '%s' column after empty '%s' column.\nRow: %s"
                        % (line, row[c], TableScan.VALUE, TableScan.WAITFOR,
                           str(row)))
                elif what == TableScan.OR_TIME:
                    raise Exception(
                        "Line %d: Found value '%s' in '%s' column after empty '%s' column.\nRow: %s"
                        % (line, row[c], TableScan.OR_TIME, TableScan.WAITFOR,
                           str(row)))
                else:
                    # 'Normal' column that sets a device directly or in loop
                    device = col_device[c]
                    value = self.__getValue(row[c])

                    if type(value) is float:
                        loop = None
                    else:
                        loop = getRangeOrLoop(str(value), loop_matcher)

                    if loop is None:
                        command = SettingsBasedSet(what, value)
                        if device.getParallel():
                            # Add one more parallel command
                            self.parallel_commands.append(command)
                        else:
                            # Normal command, flush accumulated parallel commands
                            self.__flushParallel(row_commands)
                            row_commands.append(command)
                    else:
                        # Create loop
                        self.__flushParallel(row_commands)
                        if device.getParallel():
                            raise Exception(
                                "Line %d: Cannot use loop(..) with parallel access in column '%s'"
                                % (line, what))
                        body = list()
                        command = SettingsBasedLoop(what, loop[0], loop[1],
                                                    loop[2], body)
                        row_commands.append(command)

                        # Place remaining commands in row into body of this loop
                        row_commands = command.getBody()

                    if not device.getName() in log_devices:
                        log_devices.append(device.getName())
                c = c + 1
            # End of columns in row
            # Complete accumulated parallel commands
            self.__flushParallel(row_commands)

        # End of row
        if self.post:
            # End one long run at end of table
            commands += self.post

        if lineinfo:
            commands.append(Comment("# End"))
        return commands
Exemple #3
0
Created on Apr 17, 2015

@author: shen
'''
from scan.client.scanclient import ScanClient
from scan.commands import Set, Loop, Delay, Log, Comment

from scan.client.logdata import createTable

if __name__ == '__main__':
    orig_motor_x = 0
    orig_motor_y = 0
    try:
        cmds = [
            Comment("Example"),
            Set('motor_x', 1, completion=True),
            Loop('motor_x', 1, 5, 1, [
                Loop('motor_y', 1, 3, 1, [
                    Loop('loc://i(0)', 1, 2, 1,
                         [Delay(1),
                          Log('motor_x', 'motor_y', 'neutrons')]),
                ]),
            ]),
            Set('motor_x', orig_motor_x, completion=True),
            Set('motor_y', orig_motor_y, completion=True)
        ]
        for cmd in cmds:
            print cmd
        exit()
        client = ScanClient('localhost', 4810)
Exemple #4
0
    def createScan(self):
        """Create scan.
        
        :return: List of commands.
        """
        # Parse column headers.
        settings = getScanSettings()
        col_device = [None for i in range(self.cols)]
        i = 0
        while i < self.cols:
            if self.headers[i] == TableScan.WAITFOR:
                # Column TableScan.WAITFOR must be followed by TableScan.VALUE
                if i >= self.cols - 1 or self.headers[i +
                                                      1] != TableScan.VALUE:
                    raise ValueError(TableScan.WAITFOR +
                                     " column must be followed by " +
                                     TableScan.VALUE)
                # .. and may then be followed by TableScan.OR_TIME
                if i < self.cols - 2 and self.headers[i +
                                                      2] == TableScan.OR_TIME:
                    i += 2
                else:
                    i += 1
            elif self.headers[i] in (TableScan.COMMENT):
                # Ignore other special columns
                pass
            else:
                # Parse device info
                col_device[i] = settings.parseDeviceSettings(self.headers[i])
            i += 1

        # Expand any range(start, end, step) cells
        expanded_rows = expandRanges(self.rows)

        # Assemble commands for each row in the table
        commands = list()
        log_devices = list()
        if self.log_always is not None:
            log_devices = list(self.log_always)
        if self.pre:
            commands += self.pre
        line = 0
        for row in expanded_rows:
            line += 1
            # Parallel commands to execute in this row
            parallel_commands = list()
            # Handle all columns
            i = 0
            while i < self.cols:
                what = self.headers[i]
                if len(row[i]) <= 0:
                    pass  # Empty column, nothing to do
                elif what in self.special:
                    special_handler = self.special[what]
                    value = self.__getValue(row[i])
                    command = special_handler(value)
                    commands.append(command)
                elif what == TableScan.COMMENT:
                    text = row[i]
                    commands.append(Comment(text))
                    # TODO if self.settings.comment:
                    #       commands.append(SetCommand(self.settings.comment, text))
                elif what == TableScan.WAITFOR:
                    waitfor = row[i]
                    value = self.__getValue(row[i + 1])

                    if waitfor.lower(
                    ) != TableScan.COMPLETION and parallel_commands:
                        # Complete accumulated parallel_commands before starting the run
                        commands.append(Parallel(parallel_commands))
                        parallel_commands = list()

                    # Optional commands to mark start of a "Wait For"
                    if self.start:
                        commands += self.start

                    if waitfor.lower() == TableScan.COMPLETION:
                        # Assert that there are any parallel commands,
                        # because otherwise the 'WaitFor - Completion' was likely an error
                        if not parallel_commands:
                            raise Exception(
                                "Line %d has no parallel commands to complete"
                                % line)
                        commands.append(Parallel(parallel_commands))
                        parallel_commands = list()
                    elif waitfor.lower() == TableScan.SECONDS:
                        if value:
                            commands.append(Delay(parseSeconds(value)))
                    else:
                        timeout = None
                        errhandler = None
                        if i + 2 < self.cols and self.headers[
                                i + 2] == TableScan.OR_TIME:
                            or_time = row[i + 2].strip()
                            if len(or_time) > 0:
                                timeout = parseSeconds(or_time)
                                errhandler = "OnErrorContinue"
                        cmd = SettingsBasedWait(waitfor,
                                                value,
                                                timeout=timeout,
                                                errhandler=errhandler)
                        commands.append(cmd)
                        if not waitfor in log_devices:
                            log_devices.append(waitfor)

                    if len(log_devices) > 0:
                        commands.append(Log(log_devices))

                    # Optional commands to mark end of a "Wait For"
                    if self.stop:
                        commands += self.stop

                    # Skip TableScan.VALUE in addition to current column,
                    # so next two Exceptions should not happen unless there's an empty "WAIT_FOR"
                    if i + 2 < self.cols and self.headers[
                            i + 2] == TableScan.OR_TIME:
                        i = i + 2
                    else:
                        i = i + 1
                elif what == TableScan.VALUE:
                    raise Exception(
                        "Line %d: Found value '%s' in '%s' column after empty '%s' column.\nRow: %s"
                        % (line, row[i], TableScan.VALUE, TableScan.WAITFOR,
                           str(row)))
                elif what == TableScan.OR_TIME:
                    raise Exception(
                        "Line %d: Found value '%s' in '%s' column after empty '%s' column.\nRow: %s"
                        % (line, row[i], TableScan.OR_TIME, TableScan.WAITFOR,
                           str(row)))
                else:
                    # 'Normal' column that sets a device
                    device = col_device[i]
                    value = self.__getValue(row[i])
                    command = SettingsBasedSet(what, value)

                    if device.getParallel():
                        parallel_commands.append(command)
                    else:
                        commands.append(command)

                    if not device.getName() in log_devices:
                        log_devices.append(device.getName())
                i = i + 1
            # End of columns in row
            # Complete accumulated parallel commands
            if parallel_commands:
                commands.append(Parallel(parallel_commands))
                parallel_commands = list()

        if self.post:
            # End one long run at end of table
            commands += self.post

        return commands
@author: shen
'''

from scan.client.scanclient import ScanClient
from scan import createNDimScan as ndim

from scan.commands import Comment, Delay, Set

from scan.client.logdata import createTable

if __name__ == '__main__':
    orig_motor_x = 0
    orig_motor_y = 0

    cmds = ndim(Comment("Example"), Set('motor_x', 1), ('motor_x', 1, 5, 1),
                ('motor_y', 1, 3, 1), ('loc://i(0)', 1, 2, 1),
                Set('neutrons', 0, completion=True,
                    readback=False), Delay(1), 'neutrons', 'setpoint')
    for cmd in cmds:
        print cmd
    exit()
    try:
        client = ScanClient('localhost', 4810)
        scid = client.submit(cmds, name="2D ndim scan example")
        client.waitUntilDone(scid)
        print "Number of log calls: %d" % client.lastSerial(scid)

        # get raw data back as a Python dict
        data = client.getData(scid)
        print data
@author: shen
'''

from scan.client.scanclient import ScanClient
from scan import createNDimScan as ndim

from scan.commands import Comment, Delay, Set

from scan.client.logdata import createTable


if __name__ == '__main__':
    orig_motor_x = 0
    orig_motor_y = 0
    
    cmds = ndim(Comment("Example"),
                Set('motor_x', 1),
                ('motor_x', 1, 5, 1),
                ('motor_y', 1, 3, 1),
                Set('neutrons', 0, completion=True, readback=False),
                Delay(1), 'neutrons', 'setpoint')
    try:
        client = ScanClient('localhost', 4810)
        scid = client.submit(cmds, name="2D ndim scan example")
        client.waitUntilDone(scid)
        print "Number of log calls: %d" % client.lastSerial(scid)
         
        # get raw data back as a Python dict 
        data = client.getData(scid)
        print data
         
Exemple #7
0
    def scan2d(self, device1, device2, meas_dev, **kwds):
        """ Perform a 2-D alignment scan, it checks the readback within given tolerance, 
        or waits callback completion if readback is `False`, for each setting.
        If original setting is provided, it will restore to this point after scan finished.
        If there is any error, it will try again, then abort.
    
        acceptable kwds key words:
            - title:     job title for a scan, "phyutil 1D Scan" by default
            - orig1:     original settings for `device`, default None. 
            - readback1: `False` to not check any readback,
                         `True` to wait for readback from the 'device',
                         or name of specific device to check for readback.
            - tolerance1:Tolerance when checking numeric `readback`, 0 by default.
            - orig2:     original settings for `device`, default None. 
            - readback2: `False` to not check any readback,
                         `True` to wait for readback from the 'device',
                         or name of specific device to check for readback.
            - tolerance2:Tolerance when checking numeric `readback`, 0 by default.
            - timeout:   Timeout in seconds, used for `completion` and `readback` check, 5.0 by default.
            - ramping:   ramping `device` to start at beginning, and ramp back to original after finish. 
                         If orig is not given, then ignore since not know where to go.
                         False by default.
                         `False` to directly jump to start for the 'device',
                         `True`  to ramp to start with same step for the 'device',
            - delay:     delay in seconds, 5.0 by default.
            - wait:      whether wait until done, `True` by default
            - samples:   how many point taken for each measurement device, 1 by default
            - compress:  how to compress data if multiple samples are taken, None by default.
                         Has to be:
                         `None`: no compress, and keep all data as it is;
                         `average`: take an average. 
    
        :param device1:    first dimension information with format [Device name, start, stop, step]
        :param device2:    second dimension information with format [Device name, start, stop, step]
        :param meas_dev:   Device to measure
         
        :return: a table with column following the device order: device, meas_dev
        :raise:
        
        """
        if not isinstance(device1, (list, tuple)) or len(device1) != 4 or \
                not isinstance(device2, (list, tuple)) or len(device2) != 4:
            raise RuntimeError("Scan parameters are not sufficient.")

        if not isinstance(device1[0], str):
            raise Exception("Expecting device1 name, got '%s'" %
                            str(device1[0]))
        else:
            # Ensure device is NOT unicode object until
            # it is supported by PyScanClient library.
            device1[0] = str(device1[0])

        if not isinstance(device2[0], str):
            raise Exception("Expecting device2 name, got '%s'" %
                            str(device2[0]))
        else:
            # Ensure device is NOT unicode object until
            # it is supported by PyScanClient library.
            device2[0] = str(device2[0])

        comments = kwds.get("title", "phyutil 2D Scan")
        orig1 = kwds.get("orig1", None)
        readback1 = kwds.get("readback1", False)
        tolerance1 = kwds.get("tolerance1", 0.0)
        orig2 = kwds.get("orig2", None)
        readback2 = kwds.get("readback2", False)
        tolerance2 = kwds.get("tolerance2", 0.0)
        timeout = kwds.get("timeout", 5.0)
        ramping = kwds.get("ramping", False)
        delay = kwds.get("delay", 5.0)
        samples = int(kwds.get("samples", 1))
        wait = kwds.get('wait', True)
        compress = kwds.get("compress", None)
        completion = kwds.get("completion", False)
        errhandler = kwds.get('errhandler', None)
        if compress is not None:
            # TODO add support to compress multiple samples and compress.lower not in ["average"]:
            raise RuntimeError("Compress algorithm is not support yet.")

        scan_cmds = []

        # prepare scan comments
        scan_cmds.append(Comment(comments))

        # ramp to start point if needed
        if orig1 is not None and ramping:
            # slow ramping to the start point for scan
            if orig1 < device1[1]:
                scan_cmds.append(
                    Loop(device1[0],
                         orig1,
                         device1[1],
                         abs(device1[3]), [Delay(delay)],
                         completion=completion,
                         readback=readback1,
                         tolerance=tolerance1,
                         timeout=timeout,
                         errhandler=errhandler))
            else:
                scan_cmds.append(
                    Loop(device1[0],
                         orig1,
                         device1[1],
                         -abs(device1[3]), [Delay(delay)],
                         completion=completion,
                         readback=readback1,
                         tolerance=tolerance1,
                         timeout=timeout,
                         errhandler=errhandler))
        # ramp to start point if needed
        if orig2 is not None and ramping:
            # slow ramping to the start point for scan
            if orig2 < device2[1]:
                scan_cmds.append(
                    Loop(device2[0],
                         orig2,
                         device2[1],
                         abs(device2[3]), [Delay(delay)],
                         completion=completion,
                         readback=readback2,
                         tolerance=tolerance2,
                         timeout=timeout,
                         errhandler=errhandler))
            else:
                scan_cmds.append(
                    Loop(device2[0],
                         orig2,
                         device2[1],
                         -abs(device2[3]), [Delay(delay)],
                         completion=completion,
                         readback=readback2,
                         tolerance=tolerance2,
                         timeout=timeout,
                         errhandler=errhandler))

        # confirm start point
        scan_cmds.append(
            Set(device1[0],
                device1[1],
                completion=completion,
                readback=readback1,
                tolerance=tolerance1,
                timeout=timeout,
                errhandler=errhandler))
        scan_cmds.append(
            Set(device2[0],
                device2[1],
                completion=completion,
                readback=readback2,
                tolerance=tolerance2,
                timeout=timeout,
                errhandler=errhandler))

        # real scan
        if samples == 1:
            scan_cmds.append(
                Loop(device1[0],
                     device1[1],
                     device1[2],
                     device1[3], [
                         Loop(
                             device2[0],
                             device2[1],
                             device2[2],
                             device2[3],
                             [
                                 Delay(delay),
                                 Log([device1[0], device2[0]] + list(meas_dev))
                             ],
                             completion=completion,
                             readback=readback2,
                             tolerance=tolerance2,
                         ),
                     ],
                     completion=completion,
                     readback=readback1,
                     tolerance=tolerance1,
                     timeout=timeout,
                     errhandler=errhandler))
        else:
            scan_cmds.append(
                Loop(device1[0],
                     device1[1],
                     device1[2],
                     device1[3], [
                         Loop(
                             device2[0],
                             device2[1],
                             device2[2],
                             device2[3],
                             [
                                 Loop('loc://i(0)', 1, samples, 1, [
                                     Delay(delay),
                                     Log([device1[0], device2[0]] +
                                         list(meas_dev))
                                 ])
                             ],
                             completion=completion,
                             readback=readback2,
                             tolerance=tolerance2,
                         ),
                     ],
                     completion=completion,
                     readback=readback1,
                     tolerance=tolerance1,
                     timeout=timeout,
                     errhandler=errhandler))

        # ramp back to original setting
        if orig1 is not None and ramping:
            # slow ramping to the start point for scan
            if device1[2] < orig1:
                scan_cmds.append(
                    Loop(device1[0],
                         device1[2],
                         orig1,
                         abs(device1[3]), [Delay(delay)],
                         completion=completion,
                         readback=readback1,
                         tolerance=tolerance1,
                         timeout=timeout,
                         errhandler=errhandler))
            else:
                scan_cmds.append(
                    Loop(device1[0],
                         device1[2],
                         orig1,
                         -abs(device1[3]), [Delay(delay)],
                         completion=completion,
                         readback=readback1,
                         tolerance=tolerance1,
                         timeout=timeout,
                         errhandler=errhandler))

        # ramp back to original setting
        if orig2 is not None and ramping:
            # slow ramping to the start point for scan
            if device2[2] < orig2:
                scan_cmds.append(
                    Loop(device2[0],
                         device2[2],
                         orig2,
                         abs(device2[3]), [Delay(delay)],
                         completion=completion,
                         readback=readback2,
                         tolerance=tolerance2,
                         timeout=timeout,
                         errhandler=errhandler))
            else:
                scan_cmds.append(
                    Loop(device2[0],
                         device2[2],
                         orig2,
                         -abs(device2[3]), [Delay(delay)],
                         completion=completion,
                         readback=readback2,
                         tolerance=tolerance2,
                         timeout=timeout,
                         errhandler=errhandler))
        # confirm original setting
        if orig1 is not None:
            scan_cmds.append(
                Set(device1[0],
                    orig1,
                    completion=completion,
                    readback=readback1,
                    tolerance=tolerance1,
                    timeout=timeout,
                    errhandler=errhandler))
        if orig2 is not None:
            scan_cmds.append(
                Set(device2[0],
                    orig2,
                    completion=completion,
                    readback=readback2,
                    tolerance=tolerance2,
                    timeout=timeout,
                    errhandler=errhandler))

        if self.scanclient is None:
            self._connectscanserver()

        scid = self.scanclient.submit(scan_cmds, name=comments)
        if wait:
            self.scanclient.waitUntilDone(scid)

        return scid