diff --git a/motorApp/AerotechSrc/EnsembleTrajectoryScan.h b/motorApp/AerotechSrc/EnsembleTrajectoryScan.h new file mode 100644 index 00000000..b517d76a --- /dev/null +++ b/motorApp/AerotechSrc/EnsembleTrajectoryScan.h @@ -0,0 +1,331 @@ +/* MAX_trajectoryScan.h + * + * This file is included in MAX_trajectoryScan.st. + */ + + +/* State codes for Build, Read and Execute. Careful, these must match the + * corresponding MBBI records, but there is no way to check this */ +#define BUILD_STATE_DONE 0 +#define BUILD_STATE_BUSY 1 +#define READ_STATE_DONE 0 +#define READ_STATE_BUSY 1 +#define EXECUTE_STATE_DONE 0 +#define EXECUTE_STATE_MOVE_START 1 +#define EXECUTE_STATE_EXECUTING 2 +#define EXECUTE_STATE_FLYBACK 3 + +/* Status codes for Build, Execute and Read */ +#define STATUS_UNDEFINED 0 +#define STATUS_SUCCESS 1 +#define STATUS_FAILURE 2 +#define STATUS_ABORT 3 +#define STATUS_TIMEOUT 4 + +/* Time modes */ +#define TIME_MODE_TOTAL 0 +#define TIME_MODE_PER_ELEMENT 1 + +/* Move modes */ +#define MOVE_MODE_RELATIVE 0 +#define MOVE_MODE_ABSOLUTE 1 +#define MOVE_MODE_HYBRID 2 + +/* The maximum number of axes per controller. If this is changed from 8 + * then many assign statements in this file must be changed */ +#define MAX_AXES 8 + +#define MSGSIZE 40 + +/* Define PVs */ +int debugLevel; assign debugLevel to "{P}{R}DebugLevel.VAL"; +monitor debugLevel; +int numAxes; assign numAxes to "{P}{R}NumAxes.VAL"; +monitor numAxes; +int nelements; assign nelements to "{P}{R}Nelements.VAL"; +monitor nelements; +int npulses; assign npulses to "{P}{R}Npulses.VAL"; +monitor npulses; +int startPulses; assign startPulses to "{P}{R}StartPulses.VAL"; +monitor startPulses; +int endPulses; assign endPulses to "{P}{R}EndPulses.VAL"; +monitor endPulses; +int nactual; assign nactual to "{P}{R}Nactual.VAL"; +int moveMode; assign moveMode to "{P}{R}MoveMode.VAL"; +monitor moveMode; +double time; assign time to "{P}{R}Time.VAL"; +monitor time; +double timeScale; assign timeScale to "{P}{R}TimeScale.VAL"; +monitor timeScale; +int timeMode; assign timeMode to "{P}{R}TimeMode.VAL"; +monitor timeMode; +double accel; assign accel to "{P}{R}Accel.VAL"; +monitor accel; +int build; assign build to "{P}{R}Build.VAL"; +monitor build; +int buildState; assign buildState to "{P}{R}BuildState.VAL"; +int buildStatus; assign buildStatus to "{P}{R}BuildStatus.VAL"; +string buildMessage;assign buildMessage to "{P}{R}BuildMessage.VAL"; +int simMode; assign simMode to "{P}{R}SimMode.VAL"; +monitor simMode; +int execute; assign execute to "{P}{R}Execute.VAL"; +monitor execute; +int execState; assign execState to "{P}{R}ExecState.VAL"; +monitor execState; +int execStatus; assign execStatus to "{P}{R}ExecStatus.VAL"; +string execMessage; assign execMessage to "{P}{R}ExecMessage.VAL"; +int abort; assign abort to "{P}{R}Abort.VAL"; +monitor abort; +int readback; assign readback to "{P}{R}Readback.VAL"; +monitor readback; +int readState; assign readState to "{P}{R}ReadState.VAL"; +int readStatus; assign readStatus to "{P}{R}ReadStatus.VAL"; +string readMessage; assign readMessage to "{P}{R}ReadMessage.VAL"; +double timeTrajectory[MAX_ELEMENTS]; +assign timeTrajectory to "{P}{R}TimeTraj.VAL"; +monitor timeTrajectory; +string trajectoryFile; assign trajectoryFile to "{P}{R}TrajectoryFile.VAL"; +monitor trajectoryFile; + +/*** BEGIN: Specific to MAX_trajectoryScan.st ***/ + +/* time since trajectory start */ +double elapsedTime; assign elapsedTime to "{P}{R}ElapsedTime.VAL"; + +/* Number (0..15) of general purpose I/O bit to be used for output pulses (e.g., to trigger detector). */ +int outBitNum; assign outBitNum to "{P}{R}OutBitNum.VAL"; +monitor outBitNum; + +/* Number (0..15) of general purpose I/O bit to be used for input start-trajectory pulse. */ +int inBitNum; assign inBitNum to "{P}{R}InBitNum.VAL"; +monitor inBitNum; + +/* time in seconds from trajectory start */ +double realTimeTrajectory[MAX_ELEMENTS]; +assign realTimeTrajectory to "{P}{R}realTimeTrajectory.VAL"; + +/* raw values read from the controller, generally while a trajectory is executing */ +int motorCurrentRaw[MAX_AXES]; +int motorCurrentVRaw[MAX_AXES]; +int motorCurrentARaw[MAX_AXES]; + +/* EPICS motor record resolution */ +double epicsMotorMres[MAX_AXES]; +assign epicsMotorMres to {"","","","","","","",""}; +monitor epicsMotorMres; + +/* EPICS motor-controller card number. (For now, we talk to the controller via drvMAX.cc) */ +int epicsMotorCard[MAX_AXES]; +assign epicsMotorCard to {"","","","","","","",""}; +monitor epicsMotorCard; + +/* EPICS motor record soft limits */ +double epicsMotorHLM[MAX_AXES]; +assign epicsMotorHLM to {"","","","","","","",""}; +monitor epicsMotorHLM; +double epicsMotorLLM[MAX_AXES]; +assign epicsMotorLLM to {"","","","","","","",""}; +monitor epicsMotorLLM; + +double motorMinSpeed[MAX_AXES]; +assign motorMinSpeed to + {"{P}{R}M1MinSpeed.VAL", + "{P}{R}M2MinSpeed.VAL", + "{P}{R}M3MinSpeed.VAL", + "{P}{R}M4MinSpeed.VAL", + "{P}{R}M5MinSpeed.VAL", + "{P}{R}M6MinSpeed.VAL", + "{P}{R}M7MinSpeed.VAL", + "{P}{R}M8MinSpeed.VAL"}; + +double motorMaxSpeed[MAX_AXES]; +assign motorMaxSpeed to + {"{P}{R}M1MaxSpeed.VAL", + "{P}{R}M2MaxSpeed.VAL", + "{P}{R}M3MaxSpeed.VAL", + "{P}{R}M4MaxSpeed.VAL", + "{P}{R}M5MaxSpeed.VAL", + "{P}{R}M6MaxSpeed.VAL", + "{P}{R}M7MaxSpeed.VAL", + "{P}{R}M8MaxSpeed.VAL"}; + +/*** END: Specific to MAX_trajectoryScan.st ***/ + +int moveAxis[MAX_AXES]; +assign moveAxis to + {"{P}{R}M1Move.VAL", + "{P}{R}M2Move.VAL", + "{P}{R}M3Move.VAL", + "{P}{R}M4Move.VAL", + "{P}{R}M5Move.VAL", + "{P}{R}M6Move.VAL", + "{P}{R}M7Move.VAL", + "{P}{R}M8Move.VAL"}; +monitor moveAxis; + +double motorTrajectory[MAX_AXES][MAX_ELEMENTS]; +assign motorTrajectory to + {"{P}{R}M1Traj.VAL", + "{P}{R}M2Traj.VAL", + "{P}{R}M3Traj.VAL", + "{P}{R}M4Traj.VAL", + "{P}{R}M5Traj.VAL", + "{P}{R}M6Traj.VAL", + "{P}{R}M7Traj.VAL", + "{P}{R}M8Traj.VAL"}; +monitor motorTrajectory; + +double motorReadbacks[MAX_AXES][MAX_PULSES]; +assign motorReadbacks to + {"{P}{R}M1Actual.VAL", + "{P}{R}M2Actual.VAL", + "{P}{R}M3Actual.VAL", + "{P}{R}M4Actual.VAL", + "{P}{R}M5Actual.VAL", + "{P}{R}M6Actual.VAL", + "{P}{R}M7Actual.VAL", + "{P}{R}M8Actual.VAL"}; + +double motorError[MAX_AXES][MAX_PULSES]; +assign motorError to + {"{P}{R}M1Error.VAL", + "{P}{R}M2Error.VAL", + "{P}{R}M3Error.VAL", + "{P}{R}M4Error.VAL", + "{P}{R}M5Error.VAL", + "{P}{R}M6Error.VAL", + "{P}{R}M7Error.VAL", + "{P}{R}M8Error.VAL"}; + +double motorCurrent[MAX_AXES]; +assign motorCurrent to + {"{P}{R}M1Current.VAL", + "{P}{R}M2Current.VAL", + "{P}{R}M3Current.VAL", + "{P}{R}M4Current.VAL", + "{P}{R}M5Current.VAL", + "{P}{R}M6Current.VAL", + "{P}{R}M7Current.VAL", + "{P}{R}M8Current.VAL"}; + +double motorMDVS[MAX_AXES]; +assign motorMDVS to + {"{P}{R}M1MDVS.VAL", + "{P}{R}M2MDVS.VAL", + "{P}{R}M3MDVS.VAL", + "{P}{R}M4MDVS.VAL", + "{P}{R}M5MDVS.VAL", + "{P}{R}M6MDVS.VAL", + "{P}{R}M7MDVS.VAL", + "{P}{R}M8MDVS.VAL"}; +monitor motorMDVS; + +double motorMDVA[MAX_AXES]; +assign motorMDVA to + {"{P}{R}M1MDVA.VAL", + "{P}{R}M2MDVA.VAL", + "{P}{R}M3MDVA.VAL", + "{P}{R}M4MDVA.VAL", + "{P}{R}M5MDVA.VAL", + "{P}{R}M6MDVA.VAL", + "{P}{R}M7MDVA.VAL", + "{P}{R}M8MDVA.VAL"}; + +int motorMDVE[MAX_AXES]; +assign motorMDVE to + {"{P}{R}M1MDVE.VAL", + "{P}{R}M2MDVE.VAL", + "{P}{R}M3MDVE.VAL", + "{P}{R}M4MDVE.VAL", + "{P}{R}M5MDVE.VAL", + "{P}{R}M6MDVE.VAL", + "{P}{R}M7MDVE.VAL", + "{P}{R}M8MDVE.VAL"}; + +double motorMVA[MAX_AXES]; +assign motorMVA to + {"{P}{R}M1MVA.VAL", + "{P}{R}M2MVA.VAL", + "{P}{R}M3MVA.VAL", + "{P}{R}M4MVA.VAL", + "{P}{R}M5MVA.VAL", + "{P}{R}M6MVA.VAL", + "{P}{R}M7MVA.VAL", + "{P}{R}M8MVA.VAL"}; + +int motorMVE[MAX_AXES]; +assign motorMVE to + {"{P}{R}M1MVE.VAL", + "{P}{R}M2MVE.VAL", + "{P}{R}M3MVE.VAL", + "{P}{R}M4MVE.VAL", + "{P}{R}M5MVE.VAL", + "{P}{R}M6MVE.VAL", + "{P}{R}M7MVE.VAL", + "{P}{R}M8MVE.VAL"}; + +double motorMAA[MAX_AXES]; +assign motorMAA to + {"{P}{R}M1MAA.VAL", + "{P}{R}M2MAA.VAL", + "{P}{R}M3MAA.VAL", + "{P}{R}M4MAA.VAL", + "{P}{R}M5MAA.VAL", + "{P}{R}M6MAA.VAL", + "{P}{R}M7MAA.VAL", + "{P}{R}M8MAA.VAL"}; + +int motorMAE[MAX_AXES]; +assign motorMAE to + {"{P}{R}M1MAE.VAL", + "{P}{R}M2MAE.VAL", + "{P}{R}M3MAE.VAL", + "{P}{R}M4MAE.VAL", + "{P}{R}M5MAE.VAL", + "{P}{R}M6MAE.VAL", + "{P}{R}M7MAE.VAL", + "{P}{R}M8MAE.VAL"}; + +/* We don't assign the EPICS motors here because there may be fewer than + * MAX_AXES actually in use. */ +double epicsMotorPos[MAX_AXES]; +assign epicsMotorPos to {"","","","","","","",""}; +monitor epicsMotorPos; + +int epicsMotorDir[MAX_AXES]; +assign epicsMotorDir to {"","","","","","","",""}; +monitor epicsMotorDir; + +double epicsMotorOff[MAX_AXES]; +assign epicsMotorOff to {"","","","","","","",""}; +monitor epicsMotorOff; + +double epicsMotorDone[MAX_AXES]; +assign epicsMotorDone to {"","","","","","","",""}; +monitor epicsMotorDone; + +double epicsMotorVELO[MAX_AXES]; +assign epicsMotorVELO to {"","","","","","","",""}; +monitor epicsMotorVELO; + +double epicsMotorVMAX[MAX_AXES]; +assign epicsMotorVMAX to {"","","","","","","",""}; +monitor epicsMotorVMAX; + +double epicsMotorVMIN[MAX_AXES]; +assign epicsMotorVMIN to {"","","","","","","",""}; +monitor epicsMotorVMIN; + +double epicsMotorACCL[MAX_AXES]; +assign epicsMotorACCL to {"","","","","","","",""}; +monitor epicsMotorACCL; + + +evflag buildMon; sync build buildMon; +evflag executeMon; sync execute executeMon; +evflag execStateMon; sync execState execStateMon; +evflag abortMon; sync abort abortMon; +evflag readbackMon; sync readback readbackMon; +evflag nelementsMon; sync nelements nelementsMon; +evflag motorMDVSMon; sync motorMDVS motorMDVSMon; + diff --git a/motorApp/AerotechSrc/EnsembleTrajectoryScan.st b/motorApp/AerotechSrc/EnsembleTrajectoryScan.st new file mode 100644 index 00000000..7352e933 --- /dev/null +++ b/motorApp/AerotechSrc/EnsembleTrajectoryScan.st @@ -0,0 +1,1071 @@ +program EnsembleTrajectoryScan("P=13IDC:,R=traj1,M1=M1,M2=M2,M3=M3,M4=M4,M5=M5,M6=M6,M7=M7,M8=M8,PORT=serial1") + +/* sample program invocation: + * dbLoadRecords("$(MOTOR)/motorApp/Db/trajectoryScan.db","P=xxx:,R=traj1:,NAXES=2,NELM=100,NPULSE=100") + * ... + * iocInit() + * ... + * seq &MAX_trajectoryScan, "P=xxx:,R=traj1:,M1=m1,M2=m2,M3=m3,M4=m4,M5=m5,M6=m6,M7=m7,M8=m8,PORT=none" + */ + +/* This sequencer program works with trajectoryScan.db. It implements + * coordinated trajectory motion with an Aerotech Ensemble motor controller. + * + * Tim Mooney -- based on MM4000_trajectoryScan.st by Mark Rivers. + */ + +%% #include +%% #include +%% #include +%% #include +%% #include +%% #include +%% #include +%% #include + +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define MIN(a,b) ((a) > (b) ? (b) : (a)) +#define NINT(f) (int)((f)>0 ? (f)+0.5 : (f)-0.5) + +/* This program must be compiled with the recursive option */ +option +r; + +/* Maximum # of trajectory elements. The variable motorTrajectory + * uses MAX_AXES*MAX_ELEMENTS*8 bytes in this SNL program (up to 128KB). + * Similar memory will be required for the records in the database. + * (Note that currently MAX_AXES is fixed at 8, in trajectoryScan.h.) + */ +#define MAX_ELEMENTS 1000 + +/* Maximum # of output pulses. For now, we emit a pulse at the beginning of + * every trajectory element. + */ +#define MAX_PULSES 1000 + +/* Note that MAX_ELEMENTS, and MAX_PULSES must be defined before including + * trajectoryScan.h, which defines MAX_AXES. */ +#include "EnsembleTrajectoryScan.h" + +/* Maximum size of string messages we'll be sending to the Ensemble controller */ +#define MAX_MESSAGE_STRING 100 + +/* Buffer sizes */ +#define NAME_LEN 100 + +/* Maximum size of string in EPICS string PVs. This is defined in + * epicsTypes.h, but in order to include that file it must be escaped, and then + * SNL compiler gives a warning. */ +#define MAX_STRING_SIZE 40 + +/* Polling interval in seconds for waiting for motors to reach their targets */ +#define POLL_INTERVAL (1/5.) +#define READ_INTERVAL (1/10.) + +%%char Ensemble_axis_name[] = "XYZTUVRS"; +char stringOut[MAX_MESSAGE_STRING]; +char sbuf[MAX_MESSAGE_STRING]; +char stringIn[MAX_MESSAGE_STRING]; +char abortCommand[MAX_MESSAGE_STRING]; +char *asynPort; +char *pasynUser; /* This is really asynUser* */ +int status; +int i; +int j; +int k; +int n; +double delay; +int anyMoving; +int ncomplete; +int nextra; +int npoints; +double dtime; +double dpos; +double posActual; +double posTheory; +double expectedTime; +double initialPos[MAX_AXES]; +char macroBuf[NAME_LEN]; +char motorName[NAME_LEN]; +char *p; +char *tok_save; +double frac; +double deltaV; +double v; +double vO; +int vOverride; +int lastRealTimePoint; +int doPoll; +int initStatus; +int limitViolation; +int loadingTrajectory; + +/* All PVs which will be accessed in local C functions need to have their index + * extracted with pvIndex() */ +int motorCurrentIndex[MAX_AXES]; +int epicsMotorDoneIndex[MAX_AXES]; + +#define ABORT_STATE_NONE 0 +#define ABORT_STATE_NOTED 1 +#define ABORT_STATE_SENT 2 +#define ABORT_STATE_DONE 3 + +int abortState; + +/* Note, this should be time_t, but SNL doesn't understand that. This is + * the defininition in vxWorks. */ +unsigned long startTime; +%%epicsTimeStamp eStartTime; +%%epicsTimeStamp currTime; +%%epicsTimeStamp lastPollTime; + +/* Define escaped C functions at end of file */ +%% static int writeOnly(SS_ID ssId, struct UserVar *pVar, char *command); +%% static int writeRead(SS_ID ssId, struct UserVar *pVar, char *command, char *reply); +%% static int getMotorPositions(SS_ID ssId, struct UserVar *pVar, double *pos); +%% static int getMotorMoving(SS_ID ssId, struct UserVar *pVar, int movingMask); +%% static int getEpicsMotorMoving(SS_ID ssId, struct UserVar *pVar); +%% static int waitEpicsMotors(SS_ID ssId, struct UserVar *pVar); +%% static int buildTrajectory(SS_ID ssId, struct UserVar *pVar, double *timeTrajectory, +%% double *motorTrajectory, int epicsMotorDir, int moveMode, int npoints, int npulses, double motorOffset, +%% double motorResolution, double *velocity); +%% static int loadTrajectory(SS_ID ssId, struct UserVar *pVar); +%% static int userToRaw(double user, double off, int dir, double res); +%% static double rawToUser(int raw, double off, int dir, double res); +%% static int sendReceiveCommand(SS_ID ssId, struct UserVar *pVar, char *cmd, char *callerReply); + +double position[MAX_AXES][MAX_ELEMENTS]; +double velocity[MAX_AXES][MAX_ELEMENTS]; +double motorStart[MAX_AXES]; +double motorCurr[MAX_AXES]; +double dbuf[MAX_PULSES]; + +/* variables for constructing trajectory commands */ +int movingMask; +int waitingForTrigger; + +/* temporary variables to hold mav speed and acceleration for a motor */ +double vmax; +double amax; +double d; + +ss EnsembleTrajectoryScan { + + /* Initialize things when first starting */ + state init { + when() { + initStatus = STATUS_UNDEFINED; + /* Force numAxes to be <= MAX_AXES */ + if (numAxes > MAX_AXES) numAxes = MAX_AXES; + for (i=0; istatus = pasynOctetSyncIO->connect(pVar->asynPort, 0, (asynUser **)&pVar->pasynUser, NULL); + if (status != 0) { + printf("trajectoryScan error in pasynOctetSyncIO->connect\n"); + printf(" status=%d, port=%s\n", status, asynPort); + } + for (j=0; j=1)) { + /* If nelements changes, then change endPulses to this value, + * since this is what the user normally wants. endPulses can be + * changed again after changing nelements if this is desired. */ + endPulses = nelements; + pvPut(endPulses); + } state monitor_inputs + + when(efTestAndClear(motorMDVSMon)) { + /* We don't use this. */ + } state monitor_inputs + } + + /* Build trajectory */ + state build { + when() { + /* Set busy flag while building */ + buildState = BUILD_STATE_BUSY; + pvPut(buildState); + buildStatus=STATUS_UNDEFINED; + pvPut(buildStatus); + epicsSnprintf(buildMessage, MSGSIZE, "Building..."); + pvPut(buildMessage); + + + buildStatus = STATUS_SUCCESS; /* presume we'll be successful */ + /* Initialize new trajectory */ + /* If time mode is TIME_MODE_TOTAL then construct timeTrajectory and post it */ + if (timeMode == TIME_MODE_TOTAL) { + dtime = time/(nelements-1); + for (i=0; irealTimeTrajectory, pVar->motorTrajectory[pVar->j], + %% pVar->epicsMotorDir[pVar->j], pVar->moveMode, pVar->npoints, pVar->npulses, + %% pVar->epicsMotorOff[pVar->j], pVar->epicsMotorMres[pVar->j], pVar->velocity[pVar->j]); + n += sprintf(&abortCommand[n], " @%d", j); + } + } + + /* Compute expected time for trajectory. This includes timeScale factor. */ + expectedTime = realTimeTrajectory[npoints-1]; + + /* Check trajectories against motor soft limits */ + limitViolation = 0; + for (j=0; j epicsMotorHLM[j]) || (posActual < epicsMotorLLM[j]); + if (limitViolation) { + epicsSnprintf(buildMessage, MSGSIZE, "Limit: m%d at pt. %d (%f)", j+1, k+1, posActual); + } + if (velocity[j][k]*epicsMotorMres[j] > vmax) { + limitViolation |= 1; + epicsSnprintf(buildMessage, MSGSIZE, "V limit: m%d at pt. %d (%f)", j+1, k+1, + velocity[j][k]*epicsMotorMres[j]); + } + if (k > 1) { + d = (velocity[j][k] - velocity[j][k-1]) / (realTimeTrajectory[k]-realTimeTrajectory[k-1]); + if (fabs(d) > amax) { + limitViolation |= 1; + epicsSnprintf(buildMessage, MSGSIZE, "A limit: m%d at pt. %d (%f)", j+1, k+1, d); + } + } + } + } + } + + if (limitViolation) { + buildStatus = STATUS_FAILURE; + } + + /* Set status and message string */ + + /* Clear busy flag, post status */ + buildState = BUILD_STATE_DONE; + pvPut(buildState); + pvPut(buildStatus); + pvPut(buildMessage); + /* Clear build command, post. This is a "busy" record, don't want + * to do this until build is complete. */ + build=0; + pvPut(build); + if (buildStatus == STATUS_SUCCESS) { + epicsSnprintf(buildMessage, MSGSIZE, "Done"); + pvPut(buildMessage); + } + } state monitor_inputs + } + + + state execute { + when () { + /* Set busy flag */ + execState = EXECUTE_STATE_MOVE_START; + pvPut(execState); + abortState = ABORT_STATE_NONE; + /* Set status to INVALID */ + execStatus = STATUS_UNDEFINED; + pvPut(execStatus); + /* Erase the readback and error arrays */ + for (j=0; jmotorCurr); + if (fabs(motorCurr[0] - motorStart[0]) > .01) { + printf("state execute: motor didn't move to start\n"); + execStatus = STATUS_ABORT; + %%writeOnly(ssId, pVar, pVar->abortCommand); + abortState = ABORT_STATE_SENT; + if (debugLevel) printf("abort: sent command '%s'\n", abortCommand); + } + } + + if (execStatus != STATUS_ABORT) { + %%loadTrajectory(ssId, pVar); + + %%getMotorPositions(ssId, pVar, pVar->motorStart); + %%epicsTimeGetCurrent(&lastPollTime); + + /* Get start time of execute */ + elapsedTime = 0.; + pvPut(elapsedTime); + startTime = time(0); + %%epicsTimeGetCurrent(&eStartTime); + execState = EXECUTE_STATE_EXECUTING; + pvPut(execState); + lastRealTimePoint = 0; + waitingForTrigger = ((inBitNum >= 0) && (inBitNum <= 15)); + for (j=0, movingMask = 0; jmotorCurrent); + %%epicsTimeGetCurrent(&currTime); + %% pVar->elapsedTime = epicsTimeDiffInSeconds(&currTime, &eStartTime); + %% pVar->dtime = epicsTimeDiffInSeconds(&currTime, &lastPollTime); + doPoll = dtime > POLL_INTERVAL; + for (j=0; janyMoving = getMotorMoving(ssId, pVar, pVar->movingMask); + if (anyMoving == 0) { + execState = EXECUTE_STATE_FLYBACK; + execStatus = STATUS_SUCCESS; + strcpy(execMessage, " "); + } + /* See if the elapsed time is more than twice expected, time out */ + if (elapsedTime > expectedTime*2.) { + execState = EXECUTE_STATE_FLYBACK; + execStatus = STATUS_TIMEOUT; + strcpy(execMessage, "Timeout"); + + /* abort motion of selected axes */ + %%writeOnly(ssId, pVar, pVar->abortCommand); + abortState = ABORT_STATE_SENT; + if (debugLevel) printf("timeout: sendt command '%s'\n", abortCommand); + strcpy(stringOut, "PROGRAM STOP 5"); + %%writeOnly(ssId, pVar, pVar->stringOut); + + %%waitEpicsMotors(ssId, pVar); /* wait until all motors are done */ + abortState = ABORT_STATE_DONE; + } + } + /* Check for errors while trajectories are in progress */ + } state wait_execute + + when (execState==EXECUTE_STATE_FLYBACK) { + if (debugLevel) printf("\nflyback.\n"); + pvPut(elapsedTime); + pvPut(execState); + pvPut(execStatus); + pvPut(execMessage); + /* Get the current motor positions, post them */ + %%getMotorPositions(ssId, pVar, pVar->motorCurrent); + for (j=0; jabortCommand); + if (debugLevel) printf("trajectoryAbort: sendt command '%s'\n", abortCommand); + abortState = ABORT_STATE_SENT; + + execStatus = STATUS_ABORT; + pvPut(execStatus); + strcpy(execMessage, "Abort command sent"); + pvPut(execMessage); + pvPut(elapsedTime); + /* Clear abort command, post. This is a "busy" record, don't + * want to do this until abort command has been sent. */ + abort=0; + pvPut(abort); + } + } state monitorAbort + } +} + +/***********************************************************************************************************/ +/* C functions */ +%{ + +/* writeOnly sends a command to the Ensemble controller */ +static int writeOnly(SS_ID ssId, struct UserVar *pVar, char *command) +{ + asynStatus status=0; + int debug_out=0; + size_t nwrite; + char buffer[MAX_MESSAGE_STRING]; + + /* Copy command so we can add terminator */ + strncpy(buffer, command, MAX_MESSAGE_STRING-3); + strcat(buffer, "\n"); + if (!(pVar->simMode)) { + status = pasynOctetSyncIO->write((asynUser *)pVar->pasynUser, buffer, + strlen(buffer), 1.0, &nwrite); + } + if (pVar->execState==EXECUTE_STATE_EXECUTING) + debug_out = (pVar->debugLevel >= 7); + else + debug_out = (pVar->debugLevel >= 2); + if (pVar->simMode || debug_out) printf(" writeOnly:command='%s'\n", command); + return(status); +} + + +/* writeRead sends a command to the Ensemble controller and reads the response into + * the global character buffer, stringIn. + */ +static int writeRead(SS_ID ssId, struct UserVar *pVar, char *command, char *reply) +{ + asynStatus status=0; + char buffer[MAX_MESSAGE_STRING]; + size_t nwrite, nread; + int eomReason; + + strncpy(buffer, command, MAX_MESSAGE_STRING-3); + + strcat(buffer, "\n"); + /* Use 30 second timeout, some commands take a long time to reply */ + if (!(pVar->simMode)) { + status = pasynOctetSyncIO->writeRead((asynUser *)pVar->pasynUser, buffer, + strlen(buffer), reply, MAX_MESSAGE_STRING, + 30.0, &nwrite, &nread, &eomReason); + } + if (pVar->simMode || (pVar->debugLevel >= 2)) { + printf(" writeRead:command='%s', reply='%s'\n", buffer, reply); + } + return(status); +} + + +/* getMotorPositions returns the positions of each motor */ +#define ASCII_ACK_CHAR '%' +static int getMotorPositions(SS_ID ssId, struct UserVar *pVar, double *pos) +{ + int j, status; + int dir, rawP; + double rawF; + char inputBuff[MAX_MESSAGE_STRING], outputBuff[MAX_MESSAGE_STRING]; + size_t nwrite, nread; + int eomReason; + + for (j=0; jnumAxes; j++) { + sprintf(outputBuff, "PFBKPROG(@%d)", j); + status = pasynOctetSyncIO->writeRead((asynUser *)pVar->pasynUser, outputBuff, + strlen(outputBuff), inputBuff, MAX_MESSAGE_STRING, + 30.0, &nwrite, &nread, &eomReason); + if (inputBuff[0] != ASCII_ACK_CHAR) + rawF = 0; + else + rawF = atof(&inputBuff[1]); + rawP = rawF / fabs(pVar->epicsMotorMres[j]); + if (pVar->epicsMotorDir[j] == 0) dir=1; else dir=-1; + pos[j] = rawToUser(rawP, pVar->epicsMotorOff[j], dir, pVar->epicsMotorMres[j]); + } + + if (pVar->debugLevel > 2) { + printf("\npos[0]=%f", pos[0]); + } + epicsThreadSleep(READ_INTERVAL); + return(0); +} + + +/* getMotorMoving returns 1 if any of the motors in movingMask are moving */ +static int getMotorMoving(SS_ID ssId, struct UserVar *pVar, int movingMask) +{ + char inputBuff[MAX_MESSAGE_STRING], outputBuff[MAX_MESSAGE_STRING]; + size_t nwrite, nread; + int eomReason; + int move_active; + int status; + + sprintf(outputBuff, "PLANESTATUS(0)"); + status = pasynOctetSyncIO->writeRead((asynUser *)pVar->pasynUser, outputBuff, + strlen(outputBuff), inputBuff, MAX_MESSAGE_STRING, + 30.0, &nwrite, &nread, &eomReason); + if (pVar->debugLevel > 2) { + printf("\ngetMotorMoving: inputBuff='%s'", inputBuff); + } + move_active = 0x01 & atoi(&inputBuff[1]); + + if (move_active) return(1); + return(0); +} + + +/* getEpicsMotorMoving returns the EPICS moving status of each motor, packed into + * a single int. Bit 0 = motor 1, bit 1 = motor 2, etc. 0=not moving, 1=moving. + * If the entire int is 0 then no motors are moving */ +static int getEpicsMotorMoving(SS_ID ssId, struct UserVar *pVar) +{ + int j; + int result=0, mask=0x01; + + for (j=0; jnumAxes; j++) { + seq_pvGet(ssId, pVar->epicsMotorDoneIndex[j], 0); + if (pVar->epicsMotorDone[j] == 0) result |= mask; + mask = mask << 1; + } + return(result); +} + +/* waitEpicsMotors waits for all motors to stop moving using the EPICS motor + * records.. It reads and posts the motor positions during each loop. */ +static int waitEpicsMotors(SS_ID ssId, struct UserVar *pVar) +{ + int j; + + /* Logic is that we always want to post position motor positions + * after the end of move is detected. */ + while (getEpicsMotorMoving(ssId, pVar)) { + /* Get the current motor positions, post them */ + for (j=0; jnumAxes; j++) { + pVar->motorCurrent[j] = pVar->epicsMotorPos[j]; + seq_pvPut(ssId, pVar->motorCurrentIndex[j], 0); + } + epicsThreadSleep(POLL_INTERVAL); + } + for (j=0; jnumAxes; j++) { + pVar->motorCurrent[j] = pVar->epicsMotorPos[j]; + seq_pvPut(ssId, pVar->motorCurrentIndex[j], 0); + } + return(0); +} + +/* Calculate velocities suitable for Ensemble PVT commands. + * We're given x(t) in the form x[i], t[i]. We need to calculate v(x) that will produce x(t). + * Assume someone else will take care of accelerating onto, and decelerating off of trajectory. + */ +#define VELOCITY_LINEAR 0 +static int buildTrajectory(SS_ID ssId, struct UserVar *pVar, double *realTimeTrajectory, + double *motorTrajectory, int epicsMotorDir, int moveMode, int npoints, int npulses, double motorOffset, + double motorResolution, double *velocity) +{ + double dp, dt; + int i; + +#if VELOCITY_LINEAR + for (i=0; idebugLevel > 0) { + printf("point %d: pos=%f, vel=%f, time=%f\n", i, motorTrajectory[i], velocity[i], realTimeTrajectory[i]); + } + } +#else + for (i=0; idebugLevel > 0) { + printf("point %d: pos=%f, vel=%f, time=%f\n", i, motorTrajectory[i], velocity[i], realTimeTrajectory[i]); + } + } +#endif + + return(0); +} + +static int userToRaw(double user, double off, int dir, double res) { + return (NINT((user-off)*dir/res)); +} + +static double rawToUser(int raw, double off, int dir, double res) { + /*printf("rawToUser: raw=%d, off=%f, dir=%d, res=%f, user=%f\n", raw, off, dir, res, raw*res*dir+off);*/ + return (raw*res*dir+off); +} + +/* This is going to get messy. We need to use Ensemble commands like "VELOCITY" and "PVT", + * But they aren't available via the ASCII interface we're using to send commands. So we + * use ASCII-legal commands to tell an Aerobasic program the commands we want to execute, + * and have the AeroBasic program execute those commands. + */ + +/* defines for IGLOBAL values to tell AeroBasic program which command to invoke */ +#define cmdDONE 0 +#define cmdVELOCITY_ON 1 +#define cmdVELOCITY_OFF 2 +#define cmdHALT 3 +#define cmdSTART 4 +#define cmdPVT_INIT_TIME_ABS 5 +#define cmdPVT_INIT_TIME_INC 6 +#define cmdPVT1 7 /* PVT command for one motor (PVT i1 d1, d2, TIME d3) */ +#define cmdPVT2 8 /* PVT command for two motors (PVT i1 d1, d2 i2 d3, d4 TIME d5)*/ +#define cmdPVT3 9 +#define cmdPVT4 10 +#define cmdABORT 11 +#define cmdSTARTABORT 12 + +#define cmdVar 45 +#define iarg1Var 46 +#define iarg2Var 47 +#define iarg3Var 48 +#define iarg4Var 49 +#define darg1Var 1 +#define darg2Var 2 +#define darg3Var 3 +#define numIArg 44 +#define numDArg 43 + +#define WAITLOOPS 100 + +int sendReceiveCommand(SS_ID ssId, struct UserVar *pVar, char *cmd, char *callerReply) { + int i, i1, i2, i3, i4; + double d1, d2, d3, d4, d5, d6, d7, d8, d9; + char stringOut[MAX_MESSAGE_STRING], reply[MAX_MESSAGE_STRING]; + int saveDebug; + + if (pVar->debugLevel > 0) { + printf("command: '%s'\n", cmd); + } + + saveDebug = pVar->debugLevel; + pVar->debugLevel = pVar->debugLevel - 1; + + if (strncmp(cmd, "VELOCITY ON", strlen("VELOCITY ON")) == 0) { + sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdVELOCITY_ON); + writeRead(ssId, pVar, stringOut, reply); + } else if (strncmp(cmd, "VELOCITY OFF", strlen("VELOCITY OFF")) == 0) { + sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdVELOCITY_OFF); + writeRead(ssId, pVar, stringOut, reply); + } else if (strncmp(cmd, "HALT", strlen("HALT")) == 0) { + sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdHALT); + writeRead(ssId, pVar, stringOut, reply); + } else if (strncmp(cmd, "STARTABORT", strlen("STARTABORT")) == 0) { + sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdSTARTABORT); + writeRead(ssId, pVar, stringOut, reply); + } else if (strncmp(cmd, "START", strlen("START")) == 0) { + sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdSTART); + if (callerReply != NULL) { + writeRead(ssId, pVar, stringOut, reply); + } else { + writeOnly(ssId, pVar, stringOut); + } + } else if (strncmp(cmd, "PVT INIT TIME ABS", strlen("PVT INIT TIME ABS")) == 0) { + sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdPVT_INIT_TIME_ABS); + writeRead(ssId, pVar, stringOut, reply); + } else if (strncmp(cmd, "PVT INIT TIME INC", strlen("PVT INIT TIME INC")) == 0) { + sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdPVT_INIT_TIME_INC); + writeRead(ssId, pVar, stringOut, reply); + } else if (strncmp(cmd, "PVT1", strlen("PVT1")) == 0) { + sscanf(cmd, "PVT1 %d %lf,%lf TIME %lf", &i1, &d1, &d2, &d3); + sprintf(stringOut, "IGLOBAL(%d) = %d", iarg1Var, i1); + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "DGLOBAL(%d) = %f", darg1Var, d1); + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "DGLOBAL(%d) = %f", darg2Var, d2); + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "DGLOBAL(%d) = %f", darg3Var, d3); + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdPVT1); + writeRead(ssId, pVar, stringOut, reply); + } else if (strncmp(cmd, "ABORT", strlen("ABORT")) == 0) { + i1 = i2 = i3 = i4 = -1; + i = sscanf(cmd, "ABORT @%d @%d @%d @%d", &i1, &i2, &i3, &i4); + sprintf(stringOut, "IGLOBAL(%d) = %d", numIArg, i); + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "IGLOBAL(%d) = %d", iarg1Var, i1); + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "IGLOBAL(%d) = %d", iarg2Var, i2); + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "IGLOBAL(%d) = %d", iarg3Var, i3); + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "IGLOBAL(%d) = %d", iarg4Var, i4); + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "IGLOBAL(%d) = %d", cmdVar, cmdABORT); + writeRead(ssId, pVar, stringOut, reply); + } + + /* Wait for IGLOBAL(cmdVar) to be reset to zero, but don't wait for "START" command. */ + i = 0; + if (strncmp(cmd, "START", strlen("START")) != 0) { + sprintf(stringOut, "IGLOBAL(%d)", cmdVar); + writeRead(ssId, pVar, stringOut, reply); + for (i=0; idebugLevel = saveDebug; + + if (i>WAITLOOPS-1) { + printf("sendReceiveCommand: error executing '%s'; reply='%s'\n", cmd, reply); + if (callerReply != NULL) { + printf("... reply='%s'\n", reply); + } + return(-1); + } + return(0); +} + +#define PSO_DISTANCE_ARRAY 0 +/**************************************************************************************/ +static int loadTrajectory(SS_ID ssId, struct UserVar *pVar) { + int i, j, status; + char stringOut[MAX_MESSAGE_STRING], reply[MAX_MESSAGE_STRING]; + double position, dp, dt; + int dir; + int iGlobalIndex, iGlobalIndexStart = 50; + int nPulses = 1 + pVar->endPulses - pVar->startPulses; + int intPosition, intPositionLast; + double accelDist, accelTime, decelDist, decelTime; + + pVar->loadingTrajectory = 1; + iGlobalIndex = iGlobalIndexStart; + + /* digital I/O commands */ + if ((pVar->outBitNum >= 0) && (pVar->outBitNum <= 15)) { + strcpy(stringOut, "PSOCONTROL @0 RESET"); + writeRead(ssId, pVar, stringOut, reply); + + /*strcpy(stringOut, "PSOOUTPUT @0 CONTROL 1");*/ + /*writeRead(ssId, pVar, stringOut, reply);*/ + + /* (total time, on time) inerror: two or more data types in declaration specifiers microseconds */ + /* strcpy(stringOut, "PSOPULSE @0 TIME 50,25"); */ + strcpy(stringOut, "PSOPULSE @0 TIME 5000,2500"); + writeRead(ssId, pVar, stringOut, reply); + strcpy(stringOut, "PSOOUTPUT @0 PULSE"); + writeRead(ssId, pVar, stringOut, reply); + + /* some controllers need to track source 3 instead of 1. I think 3 is the interpolated analog sin/cos */ + strcpy(stringOut, "PSOTRACK @0 INPUT 1"); + /* strcpy(stringOut, "PSOTRACK @0 INPUT 3"); */ + writeRead(ssId, pVar, stringOut, reply); + + /* Not all Ensemble controllers support the "PSODISTANCE X ARRAY" command */ +#if PSO_DISTANCE_ARRAY + /* Send a PSO pulse at every trajectory point. */ + strcpy(stringOut, "PSODISTANCE @0 ARRAY"); /* use IGLOBAL array */ + writeRead(ssId, pVar, stringOut, reply); + sprintf(stringOut, "PSOARRAY @0,%d,%d", iGlobalIndexStart, nPulses); + writeRead(ssId, pVar, stringOut, reply); +#else + /* Just send fixed-distance pulses. For now, don't even worry about StartPulses, EndPulses */ + dp = (pVar->motorTrajectory[0][pVar->npoints-1] - pVar->motorTrajectory[0][0]) / pVar->npoints; + sprintf(stringOut, "PSODISTANCE @0 FIXED %f UNITS", dp); + writeRead(ssId, pVar, stringOut, reply); +#endif + /* When motor is in taxi position, issue "PSOCONTROL @0 ARM" */ + } + + /* trajectory commands */ + getMotorPositions(ssId, pVar, pVar->motorCurrent); + + if ((pVar->outBitNum >= 0) && (pVar->outBitNum <= 15)) { + strcpy(stringOut, "PSOCONTROL @0 ARM"); + writeRead(ssId, pVar, stringOut, reply); + } + +#if PSO_DISTANCE_ARRAY + /* Program PSO array output */ + intPositionLast = 0; + for (j=0; j<1; j++) { + /* Calc accel portion of trajectory. Note epicsMotorACCL is accel time. */ + accelTime = pVar->epicsMotorACCL[j]; + accelDist = (pVar->velocity[j][0] * pVar->epicsMotorACCL[j]) / 2; + + decelTime = pVar->epicsMotorACCL[j]; + decelDist = (pVar->velocity[j][pVar->npoints - 1] * pVar->epicsMotorACCL[j]) / 2; + + if (pVar->moveAxis[j]) { + for (i=0; inpoints; i++) { + position = pVar->motorTrajectory[j][i] + accelDist; + /* enable pulses iferror: two or more data types in declaration specifiers we're within user specified range */ + if ((pVar->outBitNum >= 0) && (pVar->outBitNum <= 15)) { + if (i >= pVar->startPulses && i <= pVar->endPulses) { + /* Add this pulse-out location to the list */ + intPosition = NINT(((position - pVar->motorStart[j]) + accelDist)/pVar->epicsMotorMres[j]); + sprintf(stringOut, "IGLOBAL(%d) = %d", iGlobalIndex++, intPosition-intPositionLast); + writeRead(ssId, pVar, stringOut, reply); + intPositionLast = intPosition; + } + } + + } + } + } +#endif + + strcpy(stringOut, "TASKSTATE(5)"); + writeRead(ssId, pVar, stringOut, reply); + status = atoi(&reply[1]); + if ((reply[0] == '!') || ((status != 1) && (status != 2) && (status != 5))) { + strcpy(stringOut, "PROGRAM STOP 5"); + writeRead(ssId, pVar, stringOut, reply); + strcpy(stringOut, "TASKSTATE(5)"); + writeRead(ssId, pVar, stringOut, reply); + } + + sprintf(stringOut, "IGLOBAL(%d)=0", cmdVar); + writeRead(ssId, pVar, stringOut, reply); + strcpy(stringOut, "PROGRAM RUN 5, \"doCommand.bcx\""); + writeRead(ssId, pVar, stringOut, reply); + epicsThreadSleep(.1); + strcpy(stringOut, "PVT INIT TIME INC"); + status = sendReceiveCommand(ssId, pVar, stringOut, reply); + if (status) goto bad; + /* Don't start until I tell you to start */ + strcpy(stringOut, "HALT"); + status = sendReceiveCommand(ssId, pVar, stringOut, reply); + if (status) goto bad; + strcpy(stringOut, "VELOCITY ON"); + status = sendReceiveCommand(ssId, pVar, stringOut, reply); + if (status) goto bad; + /* Program motion */ + for (j=0; jepicsMotorACCL[j]; + accelDist = (pVar->velocity[j][0] * pVar->epicsMotorACCL[j]) / 2; + + decelTime = pVar->epicsMotorACCL[j]; + decelDist = (pVar->velocity[j][pVar->npoints - 1] * pVar->epicsMotorACCL[j]) / 2; + + if (pVar->moveAxis[j]) { + + sprintf(stringOut, "PVT1 %d %f, %f TIME %f", j, accelDist, pVar->velocity[j][0], accelTime); + status = sendReceiveCommand(ssId, pVar, stringOut, reply); + if (status) goto bad; + + for (i=1; inpoints; i++) { + if (pVar->abortState != ABORT_STATE_NONE) goto abort; + dp = pVar->motorTrajectory[j][i] - pVar->motorTrajectory[j][i-1]; + dt = pVar->realTimeTrajectory[i] - pVar->realTimeTrajectory[i-1]; + sprintf(stringOut, "PVT1 %d %f, %f TIME %f", j, dp, pVar->velocity[j][i], dt); + status = sendReceiveCommand(ssId, pVar, stringOut, reply); + if (status) goto bad; + } + strcpy(stringOut, "VELOCITY OFF"); + status = sendReceiveCommand(ssId, pVar, stringOut, reply); + if (status) goto bad; + sprintf(stringOut, "PVT1 %d %f, %f TIME %f", j, decelDist, 0., decelTime); + status = sendReceiveCommand(ssId, pVar, stringOut, reply); + if (status) goto bad; + } + } + /*epicsThreadSleep(5.);*/ + strcpy(stringOut, "START"); + /* Tell sendReceiveCommand not to wait for reply by setting reply pointer to NULL. */ + status = sendReceiveCommand(ssId, pVar, stringOut, NULL); + if (status) goto bad; + + if (pVar->abortState != ABORT_STATE_NONE) goto abort; + pVar->loadingTrajectory = 0; + return(0); + +bad: + printf("loadTrajectory: error\n"); + pVar->loadingTrajectory = 0; + return(-1); + +abort: + printf("loadTrajectory: aborted\n"); + strcpy(stringOut, "VELOCITY OFF"); + status = sendReceiveCommand(ssId, pVar, stringOut, reply); + strcpy(stringOut, "STARTABORT"); + status = sendReceiveCommand(ssId, pVar, stringOut, reply); + writeOnly(ssId, pVar, pVar->abortCommand); + pVar->abortState = ABORT_STATE_SENT; + pVar->loadingTrajectory = 0; + return(-1);} + +}% diff --git a/motorApp/AerotechSrc/Makefile b/motorApp/AerotechSrc/Makefile index b7b6ae03..0114237c 100644 --- a/motorApp/AerotechSrc/Makefile +++ b/motorApp/AerotechSrc/Makefile @@ -17,6 +17,9 @@ SRCS += drvEnsembleAsyn.cc # EnsemblePSOFly.db support SRCS += concatString.c +# Ensemble trajectory support +SRCS += EnsembleTrajectoryScan.st + Aerotech_LIBS += motor asyn Aerotech_LIBS += $(EPICS_BASE_IOC_LIBS) diff --git a/motorApp/AerotechSrc/devAerotech.dbd b/motorApp/AerotechSrc/devAerotech.dbd index 104a9ecf..8706d7bd 100644 --- a/motorApp/AerotechSrc/devAerotech.dbd +++ b/motorApp/AerotechSrc/devAerotech.dbd @@ -7,3 +7,4 @@ driver(motorEnsemble) registrar(AerotechRegister) registrar(concatStringRegister) +registrar(EnsembleTrajectoryScanRegistrar)