Commit cf4b814a authored by Emmanuel Papillon's avatar Emmanuel Papillon

* aeroprog : interface to aerotech on-controller programs

* mussttools : tools to creates scanning part of the musst
* musstscans : time trigger scans and motor diagnostic scan
parent 2588af53
Pipeline #11647 failed with stages
AeroProgErrors = {
'0': 'TaskError_NoError',
'1': 'TaskError_ArgumentOutOfBounds',
'2': 'TaskError_InvalidRegisterType',
'3': 'TaskError_PortAlreadyOpen',
'4': 'TaskError_InvalidSyncTask',
'5': 'TaskError_InvalidProgramPassword',
'6': 'TaskError_InvalidEmbeddedCommandIssued',
'7': 'TaskError_CorruptFlashMemoryFound',
'8': 'TaskError_UnimplementedCommand',
'10': 'TaskError_FeatureNotSupportedByHardware',
'11': 'TaskError_AxisCurrentlyInFault',
'12': 'TaskError_InvalidEmbeddedFunctionRev',
'82': 'TaskError_PortNotOpen',
'13': 'TaskError_TaskNotEnabled',
'14': 'TaskError_CodeSectionIsTooSmall',
'15': 'TaskError_DataSectionIsTooSmall',
'16': 'TaskError_CompilerVersionMismatch',
'17': 'TaskError_InvalidAxisGiven',
'18': 'TaskError_InvalidAxisConfiguration',
'19': 'TaskError_CNCFiveError',
'20': 'TaskError_NoJoystickPairsEnabled',
'76': 'TaskError_FeatureNotEnabled',
'94': 'TaskError_RequiredStageSerialMismatch',
'21': 'TaskError_FileNameIsTooLarge',
'22': 'TaskError_InsufficientFileSystemMemory',
'23': 'TaskError_FileCurrentlyExists',
'24': 'TaskError_FileDoesNotExist',
'25': 'TaskError_CorruptFileFound',
'26': 'TaskError_FileOptimizeInterrupt',
'27': 'TaskError_FileAccessPastEndOfFile',
'28': 'TaskError_FileNotOpen',
'29': 'TaskError_FileOpenedAsReadOnly',
'30': 'TaskError_FileOpenedAsWriteOnly',
'31': 'TaskError_NoFileHandlesAvailable',
'32': 'TaskError_FileAlreadyOpen',
'79': 'TaskError_InvalidFileName',
'95': 'TaskError_FileNameIsEmpty',
'33': 'TaskError_ReadFileBufferTooSmall',
'34': 'TaskError_FileWriteWithCRCOn',
'35': 'TaskError_HeapAllocationFailure',
'37': 'TaskError_AxisNotInTasksPlane',
'38': 'TaskError_ErrorOnFirmwareUpload',
'84': 'TaskError_FlashConfigCommitFailed',
'86': 'TaskError_OneWireDeviceNotFound',
'87': 'TaskError_OneWireDeviceCopyFailure',
'88': 'TaskError_OneWireDeviceWriteBusy',
'89': 'TaskError_OneWireDeviceReadBusy',
'39': 'TaskError_MotionActive',
'40': 'TaskError_ProfileEntryError',
'41': 'TaskError_InvalidHomeConfiguration',
'42': 'TaskError_AmplifierNotEnabled',
'43': 'TaskError_RadiusSpecifiedWithFullCircle',
'44': 'TaskError_RadiusTooShortForArc',
'45': 'TaskError_PlaneProfiling',
'46': 'TaskError_PVLengthError',
'48': 'TaskError_CamAbsIndexInCurrentPlane',
'49': 'TaskError_CamNotEnabled',
'50': 'TaskError_CamNotMonotonic',
'51': 'TaskError_CamPreviouslyEnabled',
'52': 'TaskError_CamSearchCountExceeded',
'53': 'TaskError_CamTimeOverRun',
'54': 'TaskError_FailedCamCommToSlave',
'55': 'TaskError_InvalidCamConfiguration',
'56': 'TaskError_InvalidCamContextCmd',
'57': 'TaskError_InvalidCamListSize',
'58': 'TaskError_MasterNotACammingAxis',
'59': 'TaskError_NoCamContext',
'60': 'TaskError_NotFindCamSegment',
'61': 'TaskError_InvalidFirstLastCamPos',
'62': 'TaskError_NoGantrySlaveMotion',
'75': 'TaskError_CircularRadiusError',
'77': 'TaskError_MasterMotionSuppressed',
'78': 'TaskError_InvalidTimeSpecified',
'80': 'TaskError_InvalidMotorType',
'81': 'TaskError_MXReprogramming',
'83': 'TaskError_JoystickInterlockOpen',
'85': 'TaskError_PVCannotExecute',
'90': 'TaskError_PiezoStageNotConnected',
'92': 'TaskError_AxisNumberConfigurationNotSupported',
'93': 'TaskError_AutofocusNotEnabled',
'96': 'TaskError_TrajectoryTableAlreadyRunning',
'67': 'TaskError_StackOverflow',
'68': 'TaskError_ArrayOutOfBounds',
'69': 'TaskError_DivisionByZero',
'70': 'TaskError_StringAssignmentOverflow',
'71': 'TaskError_SyncTimeOverrun',
'72': 'TaskError_TaskMonitorError',
'73': 'TaskError_OnTaskError',
'74': 'TaskError_SemaphoreStarvation'
}
class AeroProgBase:
ProgName = None
ProgPars = {}
ProgTask = 1
def __init__(self, controller, **kwargs):
self.controller = controller
self.values = dict()
self.__prepared = False
for (name, val) in kwargs.items():
self.set(name, val)
def set(self, name, value):
if name not in self.ProgPars:
raise ValueError("Invalid AeroProg parameter [{0}]".format(name))
self.values[name]= value
def read(self, name):
if name not in self.ProgPars:
raise ValueError("Invalid AeroProg parameter [{0}]".format(key))
cmd = "{0}".format(self.ProgPars[name])
return self.controller.raw_write_read(cmd)
def readall(self):
values = dict()
for (name, cmd) in self.ProgPars.items():
values[name] = self.controller.raw_write_read(cmd)
return values
def prepare(self):
for (name, value) in self.values.items():
par = self.ProgPars[name]
if par.find("IGLOBAL") >= 0:
cmd = "{0} = {1:d}".format(self.ProgPars[name], value)
elif par.find("DGLOBAL") >= 0:
cmd = "{0} = {1:f}".format(self.ProgPars[name], value)
else:
cmd = "{0} = {1}".format(self.ProgPars[name], value)
self.controller.raw_write(cmd)
self.__prepared = True
def start(self):
if not self.__prepared:
raise RuntimeError("Cannot start AeroProg not yet prepared")
cmd = "PROGRAM RUN {0},\"{1}\"".format(self.ProgTask, self.ProgName)
self.controller.raw_write(cmd)
def stop(self):
cmd = "PROGRAM STOP {0}".format(self.ProgTask)
self.controller.raw_write(cmd)
def state(self):
cmd = "TASKSTATE({0})".format(self.ProgTask)
state = self.controller.raw_write_read(cmd)
istate = int(state)
if istate == 3 or istate == 4:
return "RUNNING"
elif istate == 6:
return "ERROR"
elif istate == 0:
return "DISABLED"
else:
return "READY"
def error(self):
cmd = "TASKERROR({0})".format(self.ProgTask)
val = self.controller.raw_write_read(cmd)
err = AeroProgErrors.get(val, "Unknown [{0}]".format(val))
return err
class AeroProgTestLinear(AeroProgBase):
ProgName = "testlinear.bcx"
ProgPars = { "ntotal" : "IGLOBAL(0)" }
class AeroProgXYCont(AeroProgBase):
ProgName = "xycont.bcx"
ProgPars = {
"xdist": "DGLOBAL(0)",
"xvel": "DGLOBAL(1)",
"xacc": "DGLOBAL(2)",
"ydist": "DGLOBAL(3)",
"yvel": "DGLOBAL(4)",
"yacc": "DGLOBAL(5)",
"acqt": "DGLOBAL(6)"
}
from bliss.common.session import CURRENT_SESSION
from bliss.config.settings import ParametersWardrobe
def _get_fastscan_parameters(name, **kwargs):
parname = "{0}:{1}".format(CURRENT_SESSION.name, name)
pardict = kwargs
parkeys = list(kwargs.keys())
return ParametersWardrobe(parname,
default_values=pardict,
not_removable=parkeys)
FASTSCAN_PAR = _get_fastscan_parameters("fastscan",
axis_name="hrrz",
start_pos= 10,
velocity=90.,
period_time=0.1,
expo_time=0.08,
npoints=100)
def fastscan(fs_par=FASTSCAN_PAR):
print(fs_par)
FASTSCAN2D_PAR = _get_fastscan_parameters("fastscan2d",
slow_name="hry",
slow_start_pos=1,
slow_step=0.01,
slow_npoints=10,
fast_name="hrrz",
fast_start_pos= 10,
fast_velocity= 90.,
period_time=0.1,
expo_time=0.08,
fast_npoints=100)
def fastscan2d(fs_par=FASTSCAN2D_PAR):
print(fs_par)
import time
import numpy
from bliss.scanning.chain import AcquisitionChain
from bliss.scanning.scan import Scan
from bliss.scanning.acquisition.motor import MotorMaster
from id15.mussttools import MusstProgTimeTrigger
def mtimetrig(musst, npts, period, *motors, **kwargs):
mprog = MusstProgTimeTrigger(musst, *motors)
mprog.set_params(npts, period)
# --- initialise motor on musst
do_sync = kwargs.get("sync_motors", True)
if do_sync:
mprog.sync_motors()
# --- acquisition chain
chain = AcquisitionChain()
mprog.setup(chain)
# --- scanning
scan = Scan(chain,name='mtimetrig')
scan_t0 = time.time()
print("Start scan")
scan.run()
scan_tend = time.time() - scan_t0
print("Scan took {0:.3f} sec".format(scan_tend))
def motordiag(musst, motor, disp, speed, period):
# --- setup musst dev
acc_time = speed / motor.acceleration
move_time = disp / speed
total_time = 0.2 + 2*acc_time + move_time
npts = int(total_time / period + 0.5)
mprog = MusstProgTimeTrigger(musst, motor)
mprog.set_params(npts, period)
# --- setup motor dev
start_pos = motor.position
stop_pos = start_pos + disp
motor_master = MotorMaster(motor, start_pos, stop_pos, move_time)
# --- acq chain
chain = AcquisitionChain()
mprog.setup(chain, motor_master)
mprog.add_external_channel("timer", motor_master)
mprog.add_external_channel(motor.name, motor_master)
# --- scanning
scan = Scan(chain,name='motordiag')
scan_t0 = time.time()
print("Scan started")
scan.run()
scan_tend = time.time() - scan_t0
print("Scan took {0:.3f} sec".format(scan_tend))
import time
import numpy
import sys
from bliss.scanning.chain import AcquisitionChain
from bliss.scanning.acquisition.musst import MusstAcquisitionMaster
from bliss.scanning.acquisition.musst import MusstAcquisitionDevice
from bliss.scanning.acquisition.calc import CalcAcquisitionDevice, CalcHook
from bliss.scanning.chain import AcquisitionChannel
from bliss.common.utils import grouped
def _get_iter_var(val):
try:
_ = iter(val)
except TypeError:
return (val,)
return val
class MusstChanDiffCalc(CalcHook):
def __init__(self, master, channel):
self.channel = channel
self.name = master.name
@property
def dest_name(self):
return "{0}_diff".format(self.channel.name)
def prepare(self):
self._data = numpy.zeros((1), dtype=numpy.int32)
self._first = True
def compute(self, sender, data_dict):
data = data_dict.get(self.channel.store_name)
if data is None:
return dict()
self._data = numpy.append(self._data, data)
diff_data = (self._data[1:]-self._data[:-1]) / self.channel.resolution
if self._first:
diff_data[0]= 0
self._first= False
self._data = self._data[-1:]
return {self.dest_name : diff_data}
@property
def acquisition_channels(self):
return [AcquisitionChannel(self, self.dest_name, numpy.float,())]
class MusstChanConvCalc(CalcHook):
def __init__(self, master, channel):
self.channel = channel
self.name = master.name
@property
def dest_name(self):
return self.channel.name
def compute(self, sender, data_dict):
data = data_dict.get(self.channel.store_name)
if data is None:
return dict()
conv_data = data[:] / self.channel.resolution
return {self.dest_name : conv_data}
@property
def acquisition_channels(self):
return [AcquisitionChannel(self, self.dest_name, numpy.float,())]
class MusstChanStepCalc(CalcHook):
""" Musst Step Data Channel
channel value between 2 consecutive up triggers
"""
def __init__(self, master, channel):
self.channel = channel
self.name = master.name
@property
def dest_name(self):
return "{0}_step".format(self.channel.name)
def prepare(self):
self._data = numpy.zeros((0), dtype=numpy.int32)
def compute(self, sender, data_dict):
data = data_dict.get(self.channel.store_name)
if data is None:
return {}
self._data = numpy.append(self._data, data)
step_data = list()
last_up = self._data[0] / self.channel.resolution
for (_, up) in grouped(self._data[1:],2):
up /= self.channel.resolution
step_data.append(up - last_up)
last_up = up
if not len(step_data):
return {}
self._data= self._data[2*len(step_data):]
return {self.dest_name : numpy.array(step_data, dtype=numpy.float)}
@property
def acquisition_channels(self):
return [AcquisitionChannel(self, self.dest_name, numpy.float,())]
class MusstStoreChannel(object):
def __init__(self, name, store_name, resolution):
self.name = name
self.store_name = store_name
self.resolution = resolution
class MusstProgBase(object):
ProgramFile = None
ProgramStart = None
ProgramAbort = None
def __init__(self, musst, *motors):
self.musst = musst
self.motors = motors
self.params = dict()
self.template = dict()
def sync_motors(self):
for motor in self.motors:
self.sync_one_motor(motor)
def sync_one_motor(self, motor):
enc_chan = self.musst.get_channel_by_name(motor.name)
enc_steps = self._get_motor_resolution(motor)
motor.wait_move()
motor_pos = motor.position
enc_chan.value = int(motor_pos * enc_steps + 0.5)
def _get_motor_resolution(self, motor):
if motor.encoder:
enc_steps = motor.encoder.steps_per_unit
else:
enc_steps = motor.steps_per_unit
return enc_steps
def set_params(self, param_dict):
self.params = param_dict
def set_template(self, template_dict):
self.template = template_dict
def setup(self, chain=None, master=None):
# --- create store list and template
self.__setup_store_list_and_template()
# --- musst acquisition master
self.musst_master = MusstAcquisitionMaster(self.musst,
program = self.ProgramFile,
program_start_name = self.ProgramStart,
program_abort_name = self.ProgramAbort,
program_template_replacement = self.template,
vars = self.params)
# --- musst acquisition device
self.musst_device = MusstAcquisitionDevice(self.musst, store_list = self.store_list)
# --- add to acq. chain
if chain is None:
chain = AcquisitionChain()
if master is not None:
chain.add(master, self.musst_master)
chain.add(self.musst_master, self.musst_device)
if master is None:
self.add_external_channel("timer", self.musst_master)
self.add_conv_calc(chain)
self.add_diff_calc(chain)
self.add_step_calc(chain)
def add_external_channel(self, name, master=None):
if master is None:
master = self.musst_master
channel = self.channels[name]
chan_convert = lambda value : (value / channel.resolution)
master.add_external_channel(self.musst_device, channel.store_name, rename= channel.name,
conversion = chan_convert, dtype=numpy.float)
def add_diff_calc(self, chain, *chan_names):
if not len(chan_names):
chan_names = self.channels.keys()
for name in chan_names:
self.__add_calc_dev(chain, name, MusstChanDiffCalc)
def add_conv_calc(self, chain, *chan_names):
if not len(chan_names):
chan_names = self.channels.keys()
for name in chan_names:
self.__add_calc_dev(chain, name, MusstChanConvCalc)
def add_step_calc(self, chain, *chan_names):
if not len(chan_names):
chan_names = self.channels.keys()
for name in chan_names:
self.__add_calc_dev(chain, name, MusstChanStepCalc)
def __add_calc_dev(self, chain, name, klass):
calc_hook = klass(self.musst_device, self.channels[name])
calc_dev = CalcAcquisitionDevice(calc_hook.dest_name, (self.musst_device,), calc_hook,
calc_hook.acquisition_channels)
chain.add(self.musst_master, calc_dev)
def __setup_store_list_and_template(self):
data_alias = ""
data_store = ""
chan_names = dict()
self.channels = dict()
for motor in self.motors:
enc_chan = self.musst.get_channel_by_name(motor.name)
enc_name = '{0}_raw'.format(motor.name)
data_alias += "ALIAS DATA{0:d} = CH{0:d}\n".format(enc_chan.channel_id)
data_store += "DATA{0:d} ".format(enc_chan.channel_id)
chan_names[enc_chan.channel_id] = enc_name
self.channels[motor.name] = MusstStoreChannel(motor.name, enc_name, self._get_motor_resolution(motor))
template = { "$DATA_ALIAS$": data_alias, "$DATA_STORE$": data_store }
self.template.update(template)
self.store_list = ["timer_raw",]
if len(chan_names):
sorted_chans = list(chan_names.keys())
sorted_chans.sort()
self.store_list += [ chan_names[chid] for chid in sorted_chans ]
mclock = self.musst.get_timer_factor()
self.channels["timer"] = MusstStoreChannel("timer", "timer_raw", self.musst.get_timer_factor())
class MusstProgTimeTrigger(MusstProgBase):
ProgramFile = "timetrig.mprg"
ProgramStart = "TIMETRIG"
ProgramAbort = None
def set_params(self, npts, period):
self.npts = _get_iter_var(npts)
self.period = _get_iter_var(period)
if len(self.npts) > 10:
raise ValueError("MusstTimeTrigger is limited to 10 regions")
self.params = dict()
nzone = 0
mclock = self.musst.get_timer_factor()
for (nb, per) in zip(self.npts, self.period):
self.params["ZNPTS[{0}]".format(nzone)] = nb
self.params["ZPERIOD[{0}]".format(nzone)] = int(per * mclock)
nzone += 1
self.params["ZNB"] = nzone
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment