buggy, please don't use it in production. The commit is only done to save the progress made so far. The changes to the C804 controller and axis are caused by applying clang-format.
457 lines
20 KiB
C++
457 lines
20 KiB
C++
/**
|
|
Overview EPICS documentation
|
|
- https://docs.epics-controls.org/en/latest/index.html
|
|
- https://epics.anl.gov/modules/soft/asyn/R4-29/asynDriver.html
|
|
- https://www.physicstom.com/epics/
|
|
- https://epics.anl.gov/modules/soft/asyn/R4-20/asynDriver.pdf
|
|
*/
|
|
|
|
#include "C804Controller.h"
|
|
#include "asynOctetSyncIO.h"
|
|
#include <errlog.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <iocsh.h>
|
|
#include <epicsExport.h>
|
|
#include <registryFunction.h>
|
|
|
|
const double C804Controller::C804_TIMEOUT_ = 5.0;
|
|
|
|
static const char *driverName = "C804Controller";
|
|
/*
|
|
In SINQ, this constructor is usually called by its C interface function via the IOC shell.
|
|
A typical call looks like that (taken from SANS instrument st.cmd):
|
|
|
|
pmacAsynIPConfigure("pmcu1","sans1-mcu1:1025")
|
|
pmacV3CreateController("mcu1","pmcu1",0,9,50,10000);
|
|
pmacV3CreateAxis("mcu1",1,0);
|
|
pmacV3CreateAxis("mcu1",2,0);
|
|
pmacV3CreateAxis("mcu1",3,0);
|
|
pmacV3CreateAxis("mcu1",4,0);
|
|
pmacV3CreateAxis("mcu1",5,0);
|
|
pmacV3CreateAxis("mcu1",6,0);
|
|
pmacV3CreateAxis("mcu1",7,0);
|
|
pmacV3CreateAxis("mcu1",8,0);
|
|
|
|
The first call creates a port object in EPICS (port 1025 in this specific case) with the name "pmcu1".
|
|
|
|
The second call creates the controller with the following arguments:
|
|
- portName = "mcu1": The controller is registered by this name in EPICS. The axes
|
|
constructors below use the name to get the controller pointer from EPICS.
|
|
- lowLevelPortName = "pmcu1": The EPICS controller object connects to the physical
|
|
device via the port object "pmcu1".
|
|
- lowLevelPortAddress = 0: Connects to port 0 of asynOctetSyncIO. asynOctetSyncIO
|
|
is an interface for ASCII-string-based message communication between driver and device.
|
|
This value seems to be always zero (at least for all pmacAsynIPConfigure in the SINQ IOCs)
|
|
- numAxes = 9: Constructs the array "pAxes_" with space for 9 axis pointers (see below).
|
|
In the axes constructors below, we use the array indices 1 to 8, hence leaving the
|
|
first element of the array unitialized (it has index 0). This is mainly a convenience
|
|
to avoid talking about an "axis 0" when meaning the first axis.
|
|
- movingPollPeriod: The method C804Controller::poll is called in a loop every
|
|
movingPollPeriod seconds when the axis is moving
|
|
- idlePollPeriod: The method C804Controller::poll is called in a loop every
|
|
idlePollPeriod seconds when the axis is not moving
|
|
*/
|
|
C804Controller::C804Controller(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress,
|
|
int numAxes, double movingPollPeriod, double idlePollPeriod, const int &extraParams)
|
|
: SINQController(portName, lowLevelPortName, numAxes, extraParams)
|
|
{
|
|
|
|
// Definition of local variables.
|
|
asynStatus status = asynSuccess;
|
|
|
|
/*
|
|
functionName is overwritten by the name of the current function in each method
|
|
of C804Controller. This is used for feedback messages to the user.
|
|
*/
|
|
static const char *functionName = "C804Controller::C804Controller";
|
|
|
|
/*
|
|
Update: We don't need this array, since all axes accesses are done
|
|
with the getAxis-function, which accesses the base class array
|
|
|
|
============================================================================
|
|
|
|
The array pAxes_ is a member of the superclass asynMotorController and is
|
|
allocated when calling the constructor of asynMotorController, which
|
|
is done by the constructor of SINQController:
|
|
|
|
pAxes_ = (asynMotorAxis**) calloc(numAxes, sizeof(asynMotorAxis*));
|
|
|
|
Therefore, on this line we create a pointer to that array while interpreting
|
|
every pointer in it as one to a C804Axis. This is valid because we populate
|
|
the array in the constructor of C804Axis and can therefore be sure that
|
|
all asynMotorAxis pointers in asynMotorController::pAxes_ are in fact pointers
|
|
to C804Axis axes.
|
|
|
|
Interestingly, this array usually is leaked after destruction of asynMotorController
|
|
(n this class, we clean it up in the constructor).
|
|
The reason for that is that the EPICS devices are usually created only once at
|
|
the start of the program and the memory is cleaned up by the OS.
|
|
*/
|
|
// pAxes_ = (C804Axis **)(asynMotorController::pAxes_);
|
|
|
|
// Initialize non static data members
|
|
lowLevelPortUser_ = NULL;
|
|
movingPollPeriod_ = movingPollPeriod;
|
|
idlePollPeriod_ = idlePollPeriod;
|
|
|
|
/*
|
|
We try to connect to the port via the port name provided by the constructor.
|
|
If this fails, we return an error message.
|
|
*/
|
|
status = pasynOctetSyncIO->connect(lowLevelPortName, lowLevelPortAddress, &lowLevelPortUser_, NULL);
|
|
if (status != asynSuccess)
|
|
{
|
|
/*
|
|
ASYN_TRACE_ERROR is a mask for the trace of asynUser (member variable this->lowLevelPortUser_)
|
|
The different mask options are listed on this page: https://epics.anl.gov/modules/soft/asyn/R4-29/asynDriver.html (section AsynTrace):
|
|
|
|
0x1 ASYN_TRACE_ERROR Run time errors are reported, e.g. timeouts.
|
|
0x2 ASYN_TRACEIO_DEVICE Device support reports I/O activity.
|
|
0x4 ASYN_TRACEIO_FILTER Any layer between device support and the low level driver reports any filtering it does on I/O.
|
|
0x8 ASYN_TRACEIO_DRIVER Low level driver reports I/O activity.
|
|
0x10 ASYN_TRACE_FLOW Report logic flow. Device support should report all queue requests, callbacks entered, and all calls to drivers. Layers between device support and low level drivers should report all calls they make to lower level drivers. Low level drivers report calls they make to other support.
|
|
0x20 ASYN_TRACE_WARNING Report warnings, i.e. conditions that are between ASYN_TRACE_ERROR and ASYN_TRACE_FLOW.
|
|
|
|
To see the output of these functions e.g. on the shell, a trace mask needs
|
|
to be set:
|
|
|
|
asynSetTraceIOMask("L0", -1, 0x2)
|
|
asynSetTraceMask("L0", -1, 0x9) <- this enables 0x8 + 0x1 => ASYN_TRACE_ERROR and ASYN_TRACEIO_DRIVER
|
|
|
|
https://github-wiki-see.page/m/ISISComputingGroup/ibex_developers_manual/wiki/ASYN-Trace-Masks-(Debugging-IOC,-ASYN)
|
|
|
|
Here, we are unable to connect to the controller, which is a runtime error -> ASYN_TRACE_ERROR.
|
|
However, since lowLevelPortUser_ might still be NULL (because
|
|
pasynOctetSyncIO->connect failed for some reason), we use the alternative
|
|
EPICS function errlogPrintf. This function does not provide a timestamp
|
|
and masking facilities.
|
|
*/
|
|
// asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
|
// "%s: cannot connect to C804 controller\n",
|
|
// functionName); // Asyn-framework function, needs to be configured by setTraceMasks
|
|
errlogPrintf("Fatal error in %s: cannot connect to C804 controller\n", functionName);
|
|
exit(-1);
|
|
}
|
|
|
|
/*
|
|
Here we define the terminators for messages sent to / received from the
|
|
physical device (Eos = End of string).
|
|
In the C804 manual, the terminator for an outgoing message is specified as
|
|
(rtn) == Carriage Return. From https://en.wikipedia.org/wiki/Escape_sequences_in_C:
|
|
(rtn) => \r
|
|
|
|
An incoming report is terminated by a CRLF ETX (again referring to https://en.wikipedia.org/wiki/Escape_sequences_in_C)
|
|
CR => \r
|
|
LF (line feed) = \n
|
|
ETX (end-of-text, see https://www.asciitable.com/) => \x03
|
|
*/
|
|
const char *message_to_device = "\r";
|
|
const char *message_from_device = "\x03";
|
|
pasynOctetSyncIO->setOutputEos(lowLevelPortUser_, message_to_device, strlen(message_to_device)); // Output: from EPICS to device
|
|
pasynOctetSyncIO->setInputEos(lowLevelPortUser_, message_from_device, strlen(message_from_device)); // Input: from device to EPICS
|
|
|
|
/*
|
|
See documentation of function in asynMotorController.cpp:
|
|
"Starts the motor poller thread.
|
|
* Derived classes will typically call this at near the end of their constructor.
|
|
[...]
|
|
"
|
|
The function arguments are:
|
|
* movingPollPeriod The time between polls when any axis is moving (in seconds).
|
|
* idlePollPeriod The time between polls when no axis is moving (in seconds).
|
|
* forcedFastPolls The number of times to force the movingPollPeriod after waking up the poller.
|
|
*/
|
|
startPoller(movingPollPeriod, idlePollPeriod, 1);
|
|
|
|
/*
|
|
After changing values in the parameter library (e.g. by calls to setIntegerParam),
|
|
the PV's need to be updated. This is done explictly by callParamCallbacks()
|
|
callParamCallbacks due to the separation asyn - EPICS (param lib vs. driver support)
|
|
*/
|
|
callParamCallbacks();
|
|
}
|
|
|
|
C804Controller::~C804Controller(void)
|
|
{
|
|
/*
|
|
Cleanup of the memory allocated in the asynMotorController constructor
|
|
*/
|
|
free(this->pAxes_);
|
|
}
|
|
|
|
/*
|
|
Access one of the axes of the controller via the axis adress stored in asynUser.
|
|
If the axis does not exist or is not a C804Axis, a nullptr is returned and an error is emitted.
|
|
*/
|
|
C804Axis *C804Controller::getAxis(asynUser *pasynUser)
|
|
{
|
|
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
|
|
return C804Controller::castToC804Axis(asynAxis);
|
|
}
|
|
|
|
/*
|
|
Access one of the axes of the controller via the axis index.
|
|
If the axis does not exist or is not a C804Axis, the function must return Null
|
|
*/
|
|
C804Axis *C804Controller::getAxis(int axisNo)
|
|
{
|
|
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
|
|
return C804Controller::castToC804Axis(asynAxis);
|
|
}
|
|
|
|
C804Axis *C804Controller::castToC804Axis(asynMotorAxis *asynAxis)
|
|
{
|
|
static const char *functionName = "C804Controller::getAxis";
|
|
|
|
// If the axis slot of the pAxes_ array is empty, a nullptr must be returned
|
|
if (asynAxis == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Here, an error is emitted since asyn_axis is not a nullptr but also not an instance of C804Axis
|
|
C804Axis *axis = dynamic_cast<C804Axis *>(asynAxis);
|
|
if (axis == nullptr)
|
|
{
|
|
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR, "%s: Axis %d is not a C804 Axis", functionName, axis->axisNo_);
|
|
}
|
|
return axis;
|
|
}
|
|
|
|
/*
|
|
Sends the given command to the axis specified by axisNo and returns the response
|
|
of the axis.
|
|
*/
|
|
asynStatus C804Controller::lowLevelWriteRead(int axisNo, const char *command, char *response, bool expect_response)
|
|
{
|
|
// Definition of local variables.
|
|
static const char *functionName = "C804Controller::lowLevelWriteRead";
|
|
asynStatus status = asynSuccess;
|
|
C804Axis *axis = getAxis(axisNo);
|
|
|
|
if (axis == nullptr)
|
|
{
|
|
// We already did the error logging directly in getAxis
|
|
return asynError;
|
|
}
|
|
|
|
// TBD: Is this interpretation correct?
|
|
int eomReason = 0; // Flag indicating why the message has ended
|
|
size_t nbytesOut = 0; // Number of bytes of the outgoing message (which is command + the end-of-string terminator defined in the constructor)
|
|
size_t nbytesIn = 0; // Number of bytes of the incoming message (which is response + the end-of-string terminator defined in the constructor)
|
|
|
|
// If the class instance could not be connected to the device, set an error flag.
|
|
if (lowLevelPortUser_ == nullptr)
|
|
{
|
|
asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
|
"%s: not connected to C804 controller\n",
|
|
functionName);
|
|
|
|
// Adjust the parameter library
|
|
setIntegerParam(this->motorStatusCommsError_, 1);
|
|
return asynError;
|
|
}
|
|
|
|
// Mask ASYN_TRACEIO_DRIVER is defined as "Device support reports I/O activity"
|
|
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, "%s: command: %s\n", functionName, command);
|
|
|
|
// Writes the command to the port and blocks until a response has been received or until the timeout has been reached.
|
|
// For some inputs (such as TP = Tell position), we expect a response which is terminated by a character array "x03"
|
|
// Other messages such as MA100 (move) don't return a response
|
|
if (expect_response)
|
|
{
|
|
status = pasynOctetSyncIO->writeRead(lowLevelPortUser_,
|
|
command, strlen(command),
|
|
response, this->C804_MAXBUF_,
|
|
C804_TIMEOUT_,
|
|
&nbytesOut, &nbytesIn, &eomReason);
|
|
}
|
|
else
|
|
{
|
|
status = pasynOctetSyncIO->write(lowLevelPortUser_,
|
|
command, strlen(command),
|
|
C804_TIMEOUT_,
|
|
&nbytesOut);
|
|
}
|
|
|
|
// Writing and/or reading succeded
|
|
if (status == asynSuccess)
|
|
{
|
|
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, "%s: device response: %s\n", functionName, response);
|
|
|
|
// Reset any error which might have been set
|
|
setIntegerParam(this->motorStatusCommsError_, 0);
|
|
}
|
|
else
|
|
{
|
|
asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
|
"%s: asynOctetSyncIO->writeRead failed for command %s on axis %d\n",
|
|
functionName, command, axisNo);
|
|
|
|
setIntegerParam(this->motorStatusCommsError_, 1);
|
|
}
|
|
|
|
// Block the thread to avoid sending too many messages in a short timeframe
|
|
usleep(interMessageSleep);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*************************************************************************************/
|
|
/** The following functions are C-wrappers, and can be called directly from iocsh */
|
|
|
|
extern "C"
|
|
{
|
|
|
|
/*
|
|
C wrapper for the C804Controller constructor.
|
|
*/
|
|
asynStatus C804CreateController(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress,
|
|
int numAxes, double movingPollPeriod, double idlePollPeriod)
|
|
{
|
|
/*
|
|
We create a new instance of C804CreateController, using the "new" keyword to allocate it
|
|
on the heap while avoiding RAII.
|
|
TBD: Where is the pointer to the controller stored?
|
|
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
|
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
|
|
|
|
Setting the pointer to nullptr / NULL immediately after construction is simply
|
|
done to avoid compiler warnings, see page 7 of this document:
|
|
https://subversion.xray.aps.anl.gov/synApps/measComp/trunk/documentation/measCompTutorial.pdf
|
|
*/
|
|
C804Controller *pController = new C804Controller(portName, lowLevelPortName, lowLevelPortAddress, numAxes, movingPollPeriod, idlePollPeriod);
|
|
pController = nullptr;
|
|
|
|
return asynSuccess;
|
|
}
|
|
|
|
/*
|
|
C wrapper for the C804Axis constructor.
|
|
See C804Axis::C804Axis.
|
|
*/
|
|
asynStatus C804CreateAxis(const char *C804Name, int axis)
|
|
{
|
|
C804Axis *pAxis;
|
|
|
|
static const char *functionName = "C804CreateAxis";
|
|
|
|
/*
|
|
findAsynPortDriver is a asyn library FFI function which uses the C ABI.
|
|
Therefore it returns a void pointer instead of e.g. a pointer to a superclass
|
|
of the controller such as asynPortDriver. Type-safe upcasting via dynamic_cast
|
|
is therefore not possible directly. However, we do know that the void
|
|
pointer is either a pointer to asynPortDriver (if a driver with the specified name exists)
|
|
or a nullptr. Therefore, we first do a nullptr check, then a cast to asynPortDriver
|
|
and lastly a (typesafe) dynamic_upcast to C804Controller
|
|
https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
|
|
*/
|
|
void *ptr = findAsynPortDriver(C804Name);
|
|
if (ptr == nullptr)
|
|
{
|
|
/*
|
|
We can't use asynPrint here since this macro would require us
|
|
to get a lowLevelPortUser_ from a pointer to an asynPortDriver.
|
|
However, the given pointer is a nullptr and therefore doesn't
|
|
have a lowLevelPortUser_! printf is an EPICS alternative which
|
|
works w/o that, but doesn't offer the comfort provided
|
|
by the asynTrace-facility
|
|
*/
|
|
printf("%s:%s: Error port %s not found\n", driverName, functionName, C804Name);
|
|
return asynError;
|
|
}
|
|
// Unsafe cast of the pointer to an asynPortDriver
|
|
asynPortDriver *apd = (asynPortDriver *)(ptr);
|
|
|
|
// Safe downcast
|
|
C804Controller *pC = dynamic_cast<C804Controller *>(apd);
|
|
if (pC == nullptr)
|
|
{
|
|
printf("%s: controller on port %s is not a C804Controller\n", functionName, C804Name);
|
|
return asynError;
|
|
}
|
|
|
|
// Prevent manipulation of the controller from other threads while we create the new axis.
|
|
pC->lock();
|
|
|
|
/*
|
|
We create a new instance of C804Axis, using the "new" keyword to allocate it
|
|
on the heap while avoiding RAII. In the constructor, a pointer to the new object is stored in
|
|
the controller object "pC". Therefore, the axis instance can still be
|
|
reached later by quering "pC".
|
|
|
|
Setting the pointer to nullptr / NULL immediately after construction is simply
|
|
done to avoid compiler warnings, see page 7 of this document:
|
|
https://subversion.xray.aps.anl.gov/synApps/measComp/trunk/documentation/measCompTutorial.pdf
|
|
*/
|
|
pAxis = new C804Axis(pC, axis);
|
|
pAxis = nullptr;
|
|
|
|
// Allow manipulation of the controller again
|
|
pC->unlock();
|
|
return asynSuccess;
|
|
}
|
|
|
|
/*
|
|
This is boilerplate code which is used to make the FFI functions
|
|
C804CreateController and C804CreateAxis "known" to the IOC shell (iocsh).
|
|
TBD: If the code is compiled for running on vxWorks, this registration is
|
|
apparently not necessary?
|
|
*/
|
|
|
|
#ifdef vxWorks
|
|
#else
|
|
|
|
/*
|
|
Define name and type of the arguments for the C804CreateController function
|
|
in the iocsh. This is done by creating structs with the argument names and types
|
|
and then providing "factory" functions (configC804CreateControllerCallFunc).
|
|
These factory functions are used to register the constructors during compilation.
|
|
*/
|
|
static const iocshArg C804CreateControllerArg0 = {"Controller port name", iocshArgString};
|
|
static const iocshArg C804CreateControllerArg1 = {"Low level port name", iocshArgString};
|
|
static const iocshArg C804CreateControllerArg2 = {"Low level port address", iocshArgInt};
|
|
static const iocshArg C804CreateControllerArg3 = {"Number of axes", iocshArgInt};
|
|
static const iocshArg C804CreateControllerArg4 = {"Moving poll rate (s)", iocshArgDouble};
|
|
static const iocshArg C804CreateControllerArg5 = {"Idle poll rate (s)", iocshArgDouble};
|
|
static const iocshArg *const C804CreateControllerArgs[] = {&C804CreateControllerArg0,
|
|
&C804CreateControllerArg1,
|
|
&C804CreateControllerArg2,
|
|
&C804CreateControllerArg3,
|
|
&C804CreateControllerArg4,
|
|
&C804CreateControllerArg5};
|
|
static const iocshFuncDef configC804CreateController = {"C804CreateController", 6, C804CreateControllerArgs};
|
|
static void configC804CreateControllerCallFunc(const iocshArgBuf *args)
|
|
{
|
|
C804CreateController(args[0].sval, args[1].sval, args[2].ival, args[3].ival, args[4].dval, args[5].dval);
|
|
}
|
|
|
|
/*
|
|
Same procedure as for the C804CreateController function, but for the axis itself.
|
|
*/
|
|
static const iocshArg C804CreateAxisArg0 = {"Controller port name", iocshArgString};
|
|
static const iocshArg C804CreateAxisArg1 = {"Axis number", iocshArgInt};
|
|
static const iocshArg *const C804CreateAxisArgs[] = {&C804CreateAxisArg0,
|
|
&C804CreateAxisArg1};
|
|
static const iocshFuncDef configC804CreateAxis = {"C804CreateAxis", 2, C804CreateAxisArgs};
|
|
static void configC804CreateAxisCallFunc(const iocshArgBuf *args)
|
|
{
|
|
C804CreateAxis(args[0].sval, args[1].ival);
|
|
}
|
|
|
|
// This function is made known to EPICS in sinq.dbd and is called by EPICS
|
|
// in order to register both functions in the IOC shell
|
|
// TBD: Does this happen during compilation?
|
|
static void C804ControllerRegister(void)
|
|
{
|
|
iocshRegister(&configC804CreateController, configC804CreateControllerCallFunc);
|
|
iocshRegister(&configC804CreateAxis, configC804CreateAxisCallFunc);
|
|
}
|
|
epicsExportRegistrar(C804ControllerRegister);
|
|
|
|
#endif
|
|
|
|
} // extern "C"
|