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')]" )
def testSpecialColumns(self): print("\n=== Special columns ===") # Commands Start/Next/End turn into Include("lf_start.scn"), Include("lf_next.scn") resp. Include("lf_end.scn") special = { 'Run Control': lambda cell: Include(cell + ".scn"), 'Delay': lambda cell: Delay(parseSeconds(cell)), } table_scan = TableScan(( "Run Control", "X", "Delay", "Wait For", "Value", ), [ ["Start", "10", "", "Neutrons", "10"], ["", "20", "00:01:00", "Neutrons", "10"], ["Stop", "", "", "", ""], ], special=special) cmds = handle(table_scan) self.assertEqual( str(cmds), "[Include('Start.scn'), Set('X', 10.0), Wait('Neutrons', 10.0, comparison='>=', tolerance=0.1), Log('X', 'Neutrons'), Set('X', 20.0), Delay(60), Wait('Neutrons', 10.0, comparison='>=', tolerance=0.1), Log('X', 'Neutrons'), Include('Stop.scn')]" )
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
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) scid = client.submit(cmds, name="2D scan example") client.waitUntilDone(scid) print "Number of log calls: %d" % client.lastSerial(scid) # get raw data back as a Python dict
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
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 = 0 try: client = ScanClient('localhost', 4810) cmds = [ Comment("Example"), Set('motor_x', 1, completion=True), Loop('motor_x', 1, 10, 1, [Delay(1.0), Log("motor_x", "neutrons")], completion=True), Set('motor_x', orig_motor, completion=True) ] scid = client.submit(cmds, name="1D scan example") client.waitUntilDone(scid) data = client.getData(scid) # Create table for motor_x and neutrons table = createTable(data, 'motor_x', 'neutrons') print "Positions: ", table[0] print "Counts : ", table[1] # Remove specific scan task client.delete(scid)
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 # Create table for motor_x, motor_y and neutrons table = createTable(data, 'motor_x', 'motor_y', 'neutrons', 'setpoint')
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 # Create table for motor_x, motor_y and neutrons table = createTable(data, 'motor_x', 'motor_y', 'neutrons', 'setpoint') print "Position X: ", table[0] print "Position Y: ", table[1] print "Counts : ", table[2]
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, [Delay(1), Log('motor_x', 'motor_y', 'neutrons')]), ]), Set('motor_x', orig_motor_x, completion=True), Set('motor_y', orig_motor_y, completion=True) ] client = ScanClient('localhost', 4810) scid = client.submit(cmds, name="2D 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) # Create table for motor_x, motor_y and neutrons table = createTable(data, 'motor_x', 'motor_y', 'neutrons') print "Position X: ", table[0]
from scan.client.logdata import createTable if __name__ == '__main__': orig_motor = 0 try: client = ScanClient('localhost', 4810) cmds = [ Comment("Example"), Set('motor_x', 1, completion=True), Loop('motor_x', 1, 5, 1, [ Loop('loc://i(0)', 1, 3, 1, [Log('motor_x', 'neutrons'), Delay(1)]) ], completion=True), Set('motor_x', orig_motor, completion=True) ] scid = client.submit(cmds, name="1D scan example") client.waitUntilDone(scid) data = client.getData(scid) import pprint pprint.pprint(data) # Create table for motor_x and neutrons table = createTable(data, 'motor_x', 'neutrons') print "Positions: ", table[0] print "Counts : ", table[1]
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