Initial commit for masterMacs
This commit is contained in:
652
src/masterMacsController.cpp
Normal file
652
src/masterMacsController.cpp
Normal file
@@ -0,0 +1,652 @@
|
||||
|
||||
#include "masterMacsController.h"
|
||||
#include "asynMotorController.h"
|
||||
#include "asynOctetSyncIO.h"
|
||||
#include "masterMacsAxis.h"
|
||||
#include <epicsExport.h>
|
||||
#include <errlog.h>
|
||||
#include <iocsh.h>
|
||||
#include <netinet/in.h>
|
||||
#include <registryFunction.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* @brief Copy src into dst and replace all NULL terminators up to the carriage
|
||||
* return with spaces. This allows to print *dst with asynPrint.
|
||||
*
|
||||
* @param dst Buffer for the modified string
|
||||
* @param src Original string
|
||||
*/
|
||||
void adjustForPrint(char *dst, const char *src, size_t buf_length) {
|
||||
for (size_t i = 0; i < buf_length; i++) {
|
||||
if (src[i] == '\x0d') {
|
||||
dst[i] = ' ';
|
||||
break;
|
||||
} else if (src[i] == '\x00') {
|
||||
dst[i] = ' ';
|
||||
} else {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new masterMacsController::masterMacsController object
|
||||
*
|
||||
* @param portName See documentation of sinqController
|
||||
* @param ipPortConfigName See documentation of sinqController
|
||||
* @param numAxes See documentation of sinqController
|
||||
* @param movingPollPeriod See documentation of sinqController
|
||||
* @param idlePollPeriod See documentation of sinqController
|
||||
* @param comTimeout Time after which a communication timeout error
|
||||
* is declared in writeRead (in seconds)
|
||||
* @param extraParams See documentation of sinqController
|
||||
*/
|
||||
masterMacsController::masterMacsController(const char *portName,
|
||||
const char *ipPortConfigName,
|
||||
int numAxes, double movingPollPeriod,
|
||||
double idlePollPeriod,
|
||||
double comTimeout)
|
||||
: sinqController(
|
||||
portName, ipPortConfigName, numAxes, movingPollPeriod, idlePollPeriod,
|
||||
/*
|
||||
The following parameter library entries are added in this driver:
|
||||
- REREAD_ENCODER_POSITION
|
||||
- READ_CONFIG
|
||||
*/
|
||||
NUM_masterMacs_DRIVER_PARAMS)
|
||||
|
||||
{
|
||||
|
||||
// Initialization of local variables
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
// Initialization of all member variables
|
||||
lowLevelPortUser_ = nullptr;
|
||||
comTimeout_ = comTimeout;
|
||||
|
||||
// =========================================================================;
|
||||
|
||||
/*
|
||||
We try to connect to the port via the port name provided by the constructor.
|
||||
If this fails, the function is terminated via exit
|
||||
*/
|
||||
pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL);
|
||||
if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
|
||||
errlogPrintf(
|
||||
"%s => line %d:\nFATAL ERROR (cannot connect to MCU controller).\n"
|
||||
"Terminating IOC",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/*
|
||||
Define the end-of-string of a message coming from the device to EPICS.
|
||||
It is not necessary to append a terminator to outgoing messages, since
|
||||
the message length is encoded in the message header in the getSetResponse
|
||||
method.
|
||||
*/
|
||||
const char *message_from_device = "\x03"; // Hex-code for ETX
|
||||
status = pasynOctetSyncIO->setInputEos(
|
||||
lowLevelPortUser_, message_from_device, strlen(message_from_device));
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nFATAL ERROR (setting input EOS failed "
|
||||
"with %s).\nTerminating IOC",
|
||||
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
|
||||
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = callParamCallbacks();
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(
|
||||
this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nFATAL ERROR (executing ParamLib callbacks failed "
|
||||
"with %s).\nTerminating IOC",
|
||||
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
|
||||
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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 Axis, a nullptr is returned and an
|
||||
error is emitted.
|
||||
*/
|
||||
masterMacsAxis *masterMacsController::getAxis(asynUser *pasynUser) {
|
||||
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
|
||||
return masterMacsController::castToAxis(asynAxis);
|
||||
}
|
||||
|
||||
/*
|
||||
Access one of the axes of the controller via the axis index.
|
||||
If the axis does not exist or is not a Axis, the function must return Null
|
||||
*/
|
||||
masterMacsAxis *masterMacsController::getAxis(int axisNo) {
|
||||
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
|
||||
return masterMacsController::castToAxis(asynAxis);
|
||||
}
|
||||
|
||||
masterMacsAxis *masterMacsController::castToAxis(asynMotorAxis *asynAxis) {
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// 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 Axis
|
||||
masterMacsAxis *axis = dynamic_cast<masterMacsAxis *>(asynAxis);
|
||||
if (axis == nullptr) {
|
||||
asynPrint(
|
||||
this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nAxis %d is not an instance of masterMacsAxis",
|
||||
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
|
||||
}
|
||||
return axis;
|
||||
}
|
||||
|
||||
asynStatus masterMacsController::writeRead(int axisNo, const char *command,
|
||||
char *response,
|
||||
bool expectResponse) {
|
||||
|
||||
// Definition of local variables.
|
||||
asynStatus status = asynSuccess;
|
||||
asynStatus pl_status = asynSuccess;
|
||||
std::string fullCommand = "";
|
||||
char fullResponse[MAXBUF_] = {0};
|
||||
char printableCommand[MAXBUF_] = {0};
|
||||
char printableResponse[MAXBUF_] = {0};
|
||||
char drvMessageText[MAXBUF_] = {0};
|
||||
int motorStatusProblem = 0;
|
||||
|
||||
// Send the message and block the thread until either a response has
|
||||
// been received or the timeout is triggered
|
||||
int eomReason = 0; // Flag indicating why the message has ended
|
||||
|
||||
// Number of bytes of the outgoing message (which is command + the
|
||||
// end-of-string terminator defined in the constructor)
|
||||
size_t nbytesOut = 0;
|
||||
|
||||
// Number of bytes of the incoming message (which is response + the
|
||||
// end-of-string terminator defined in the constructor)
|
||||
size_t nbytesIn = 0;
|
||||
|
||||
// These parameters are used to remove not-needed information from the
|
||||
// response.
|
||||
int dataStartIndex = 0;
|
||||
int validIndex = 0;
|
||||
bool responseValid = false;
|
||||
|
||||
// =========================================================================
|
||||
|
||||
masterMacsAxis *axis = getAxis(axisNo);
|
||||
if (axis == nullptr) {
|
||||
// We already did the error logging directly in getAxis
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/*
|
||||
PSI SINQ uses a custom protocol which is described in
|
||||
PSI_TCP_Interface_V1-8.pdf (p. // 4-17).
|
||||
A special case is the message length, which is specified by two bytes LSB
|
||||
and MSB:
|
||||
MSB = message length / 256
|
||||
LSB = message length % 256.
|
||||
For example, a message length of 47 chars would result in MSB = 0, LSB = 47,
|
||||
whereas a message length of 356 would result in MSB = 1, LSB = 100.
|
||||
|
||||
The full protocol looks as follows:
|
||||
0x05 -> Start of protocol frame ENQ
|
||||
[LSB]
|
||||
[MSB]
|
||||
0x19 -> Data type PDO1
|
||||
value [Actual message] It is not necessary to append a terminator, since
|
||||
this protocol encodes the message length in LSB and MSB.
|
||||
0x0D -> Carriage return (ASCII alias \r)
|
||||
0x03 -> End of text ETX
|
||||
|
||||
The rest of the telegram length is filled with 0x00 as specified in the
|
||||
protocol.
|
||||
*/
|
||||
|
||||
// Command has four additional bytes between ENQ and ETX:
|
||||
// LSB, MSB, PDO1 and CR
|
||||
const size_t commandLength = strlen(command) + 4;
|
||||
|
||||
// Perform both division and modulo operation at once.
|
||||
div_t commandLengthSep = std::div(commandLength, 256);
|
||||
|
||||
/*
|
||||
Build the actual command. LSB and MSB need to be converted directly to hex,
|
||||
hence they are interpolated as characters. For example, the eight hex value
|
||||
is 0x08, but interpolating 8 directly via %x or %d returns the 38th hex
|
||||
value, since 8 is interpreted as ASCII in those cases.
|
||||
*/
|
||||
fullCommand.push_back('\x05');
|
||||
fullCommand.push_back(commandLengthSep.rem);
|
||||
fullCommand.push_back(commandLengthSep.quot);
|
||||
fullCommand.push_back('\x19');
|
||||
for (size_t i = 0; i < strlen(command); i++) {
|
||||
fullCommand.push_back(command[i]);
|
||||
}
|
||||
fullCommand.push_back('\x0D');
|
||||
fullCommand.push_back('\x03');
|
||||
// snprintf(fullCommand, MAXBUF_, "\x05%c%c\x19%s\x0D\x03",
|
||||
// commandLengthSep.rem, commandLengthSep.quot, command);
|
||||
|
||||
adjustForPrint(printableCommand, fullCommand.c_str(), MAXBUF_);
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
|
||||
"%s => line %d:\nSending command %s\n", __PRETTY_FUNCTION__,
|
||||
__LINE__, printableCommand);
|
||||
|
||||
// Perform the actual writeRead
|
||||
status = pasynOctetSyncIO->writeRead(
|
||||
lowLevelPortUser_, fullCommand.c_str(), fullCommand.length(),
|
||||
fullResponse, MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn, &eomReason);
|
||||
|
||||
// ___________________________________________________________DEBUG
|
||||
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
// "=================== COMMAND ===================\n");
|
||||
|
||||
// for (int i = 0; i < 12; ++i) {
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
||||
// fullCommand[i], fullCommand[i]);
|
||||
// }
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "\n");
|
||||
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
// "=================== RESPONSE ===================\n");
|
||||
|
||||
// for (int i = 0; i < 40; ++i) {
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
||||
// fullResponse[i], fullResponse[i]);
|
||||
// }
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "\n");
|
||||
|
||||
// ___________________________________________________________DEBUG
|
||||
|
||||
/*
|
||||
As of 19.12.2025, there is a bug in the MasterMACS hardware: If two commands
|
||||
are sent in rapid succession, MasterMACS sends garbage as answer for the
|
||||
second command. Crucially, this garbage does not contain an CR. Hence, the
|
||||
following strategy is implemented here:
|
||||
- Wait 1 ms after a pasynOctetSyncIO->writeRead
|
||||
- If the message does not contain an CR, wait 50 ms and then try again. If
|
||||
we receive garbage again, propagate the error.
|
||||
*/
|
||||
// Check for CR
|
||||
bool hasEtx = false;
|
||||
for (ulong i = 0; i < sizeof(fullResponse); i++) {
|
||||
if (fullResponse[i] == '\x0d') {
|
||||
hasEtx = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasEtx) {
|
||||
usleep(50000); // 50 ms
|
||||
|
||||
// Try again ...
|
||||
status = pasynOctetSyncIO->writeRead(
|
||||
lowLevelPortUser_, fullCommand.c_str(), fullCommand.length(),
|
||||
fullResponse, MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn,
|
||||
&eomReason);
|
||||
|
||||
// Does this message contain an CR?
|
||||
for (ulong i = 0; i < sizeof(fullResponse); i++) {
|
||||
if (fullResponse[i] == '\x0d') {
|
||||
hasEtx = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasEtx) {
|
||||
adjustForPrint(printableCommand, fullCommand.c_str(), MAXBUF_);
|
||||
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
||||
// Failed for the second time => Give up and propagate the error.
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nReceived garbage response '%s' for "
|
||||
"command '%s' two times in a row.\n",
|
||||
__PRETTY_FUNCTION__, __LINE__, printableResponse,
|
||||
printableCommand);
|
||||
status = asynError;
|
||||
} else {
|
||||
usleep(1000); // 1 ms
|
||||
}
|
||||
} else {
|
||||
usleep(50000); // 1 ms
|
||||
}
|
||||
|
||||
// Create custom error messages for different failure modes
|
||||
switch (status) {
|
||||
case asynSuccess:
|
||||
|
||||
/*
|
||||
A response looks like this (note the spaces, they are part of the
|
||||
message!):
|
||||
- [ENQ][LSB][MSB][PDO1] 1 R 2=12.819[ACK][CR] (No error)
|
||||
- [ENQ][LSB][MSB][PDO1] 1 R 2=12.819[NAK][CR] (Communication failed)
|
||||
- [ENQ][LSB][MSB][PDO1] 1 S 10 [CAN][CR] (Driver tried to write with a
|
||||
read-only command)
|
||||
Read out the second-to-last char of the response and check if it is NAK
|
||||
or CAN.
|
||||
*/
|
||||
|
||||
// We don't use strlen here since the C string terminator 0x00 occurs in
|
||||
// the middle of the char array.
|
||||
for (ulong i = 0; i < sizeof(fullResponse); i++) {
|
||||
if (fullResponse[i] == '=') {
|
||||
dataStartIndex = i + 1;
|
||||
}
|
||||
if (fullResponse[i] == '\x06') {
|
||||
validIndex = i;
|
||||
responseValid = true;
|
||||
break;
|
||||
} else if (fullResponse[i] == '\x15') {
|
||||
// NAK
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"Communication failed.");
|
||||
responseValid = false;
|
||||
break;
|
||||
} else if (fullResponse[i] == '\x18') {
|
||||
// CAN
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"Tried to write with a read-only command. This is a "
|
||||
"bug, please call the support.");
|
||||
responseValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (responseValid) {
|
||||
/*
|
||||
If a property has been read, we need just the part between the "="
|
||||
(0x3D) and the [ACK] (0x06). Therefore, we remove all non-needed
|
||||
parts after evaluating the second-to-last char before returning the
|
||||
response.
|
||||
*/
|
||||
for (int i = 0; i + dataStartIndex < validIndex; i++) {
|
||||
response[i] = fullResponse[i + dataStartIndex];
|
||||
}
|
||||
} else {
|
||||
status = asynError;
|
||||
}
|
||||
|
||||
break; // Communicate nothing
|
||||
case asynTimeout:
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"connection timeout for axis %d", axisNo);
|
||||
break;
|
||||
case asynDisconnected:
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"axis is not connected");
|
||||
break;
|
||||
case asynDisabled:
|
||||
snprintf(drvMessageText, sizeof(drvMessageText), "axis is disabled");
|
||||
break;
|
||||
default:
|
||||
snprintf(drvMessageText, sizeof(drvMessageText),
|
||||
"Communication failed (%s)", stringifyAsynStatus(status));
|
||||
break;
|
||||
}
|
||||
|
||||
// ___________________________________________________________DEBUG
|
||||
|
||||
// adjustForPrint(printableCommand, fullCommand, MAXBUF_);
|
||||
// adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
||||
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
// "\n------------------c\n"); for (int i = 0; i < 12; ++i) {
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
||||
// fullCommand[i], fullCommand[i]);
|
||||
// }
|
||||
// for (int i = 0; i < 12; ++i) {
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
||||
// printableCommand[i], printableCommand[i]);
|
||||
// }
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
// "\n=================\n"); for (int i = 0; i < 12; ++i) {
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
||||
// fullResponse[i], fullResponse[i]);
|
||||
// }
|
||||
// for (int i = 0; i < 12; ++i) {
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "%x: %c\n",
|
||||
// printableResponse[i], printableResponse[i]);
|
||||
// }
|
||||
// asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
// "\n++++++++++++++++++++\n");
|
||||
|
||||
// asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
// "%s => line %d:\nResponse '%s' for command '%s'.\n",
|
||||
// __PRETTY_FUNCTION__, __LINE__, printableResponse,
|
||||
// printableCommand);
|
||||
// ___________________________________________________________DEBUG
|
||||
|
||||
// Log the overall status (communication successfull or not)
|
||||
if (status == asynSuccess) {
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
|
||||
"%s => line %d:\nReturn value: %s\n", __PRETTY_FUNCTION__,
|
||||
__LINE__, response);
|
||||
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
|
||||
} else {
|
||||
adjustForPrint(printableCommand, fullCommand.c_str(), MAXBUF_);
|
||||
asynPrint(
|
||||
lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nCommunication failed for command '%s' (%s)\n",
|
||||
__PRETTY_FUNCTION__, __LINE__, printableCommand,
|
||||
stringifyAsynStatus(status));
|
||||
|
||||
// Check if the axis already is in an error communication mode. If it is
|
||||
// not, upstream the error. This is done to avoid "flooding" the user
|
||||
// with different error messages if more than one error ocurred before
|
||||
// an error-free communication
|
||||
pl_status =
|
||||
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
|
||||
if (pl_status != asynSuccess) {
|
||||
return paramLibAccessFailed(pl_status, "motorStatusProblem_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
if (motorStatusProblem == 0) {
|
||||
pl_status = axis->setStringParam(motorMessageText_, drvMessageText);
|
||||
if (pl_status != asynSuccess) {
|
||||
return paramLibAccessFailed(pl_status, "motorMessageText_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
|
||||
if (pl_status != asynSuccess) {
|
||||
return paramLibAccessFailed(pl_status, "motorStatusProblem",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
|
||||
if (pl_status != asynSuccess) {
|
||||
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
|
||||
// masterMacs can be disabled
|
||||
if (pasynUser->reason == motorCanDisable_) {
|
||||
*value = 1;
|
||||
return asynSuccess;
|
||||
} else {
|
||||
return asynMotorController::readInt32(pasynUser, value);
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************************/
|
||||
/** The following functions are C-wrappers, and can be called directly from
|
||||
* iocsh */
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
C wrapper for the controller constructor. Please refer to the
|
||||
masterMacsController constructor documentation.
|
||||
*/
|
||||
asynStatus masterMacsCreateController(const char *portName,
|
||||
const char *ipPortConfigName, int numAxes,
|
||||
double movingPollPeriod,
|
||||
double idlePollPeriod,
|
||||
double comTimeout) {
|
||||
/*
|
||||
We create a new instance of the controller, using the "new" keyword to
|
||||
allocate it on the heap while avoiding RAII.
|
||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
||||
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
|
||||
|
||||
The created object is registered in EPICS in its constructor and can safely
|
||||
be "leaked" here.
|
||||
*/
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
masterMacsController *pController =
|
||||
new masterMacsController(portName, ipPortConfigName, numAxes,
|
||||
movingPollPeriod, idlePollPeriod, comTimeout);
|
||||
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
C wrapper for the axis constructor. Please refer to the masterMacsAxis
|
||||
constructor documentation. The controller is read from the portName.
|
||||
*/
|
||||
asynStatus masterMacsCreateAxis(const char *portName, int axis) {
|
||||
masterMacsAxis *pAxis;
|
||||
|
||||
/*
|
||||
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 Controller
|
||||
https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
|
||||
*/
|
||||
void *ptr = findAsynPortDriver(portName);
|
||||
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
|
||||
*/
|
||||
errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
|
||||
__LINE__, portName);
|
||||
return asynError;
|
||||
}
|
||||
// Unsafe cast of the pointer to an asynPortDriver
|
||||
asynPortDriver *apd = (asynPortDriver *)(ptr);
|
||||
|
||||
// Safe downcast
|
||||
masterMacsController *pC = dynamic_cast<masterMacsController *>(apd);
|
||||
if (pC == nullptr) {
|
||||
errlogPrintf("%s => line %d:\ncontroller on port %s is not a "
|
||||
"masterMacsController.",
|
||||
__PRETTY_FUNCTION__, __LINE__, portName);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// Prevent manipulation of the controller from other threads while we
|
||||
// create the new axis.
|
||||
pC->lock();
|
||||
|
||||
/*
|
||||
We create a new instance of the axis, using the "new" keyword to
|
||||
allocate it on the heap while avoiding RAII.
|
||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
||||
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
|
||||
|
||||
The created object is registered in EPICS in its constructor and can safely
|
||||
be "leaked" here.
|
||||
*/
|
||||
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
pAxis = new masterMacsAxis(pC, axis);
|
||||
|
||||
// Allow manipulation of the controller again
|
||||
pC->unlock();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
This is boilerplate code which is used to make the FFI functions
|
||||
CreateController and CreateAxis "known" to the IOC shell (iocsh).
|
||||
*/
|
||||
|
||||
#ifdef vxWorks
|
||||
#else
|
||||
|
||||
/*
|
||||
Define name and type of the arguments for the CreateController function
|
||||
in the iocsh. This is done by creating structs with the argument names and
|
||||
types and then providing "factory" functions
|
||||
(configCreateControllerCallFunc). These factory functions are used to
|
||||
register the constructors during compilation.
|
||||
*/
|
||||
static const iocshArg CreateControllerArg0 = {"Controller name (e.g. mmacs1)",
|
||||
iocshArgString};
|
||||
static const iocshArg CreateControllerArg1 = {
|
||||
"Asyn IP port name (e.g. pmmacs1)", iocshArgString};
|
||||
static const iocshArg CreateControllerArg2 = {"Number of axes", iocshArgInt};
|
||||
static const iocshArg CreateControllerArg3 = {"Moving poll rate (s)",
|
||||
iocshArgDouble};
|
||||
static const iocshArg CreateControllerArg4 = {"Idle poll rate (s)",
|
||||
iocshArgDouble};
|
||||
static const iocshArg CreateControllerArg5 = {"Communication timeout (s)",
|
||||
iocshArgDouble};
|
||||
static const iocshArg *const CreateControllerArgs[] = {
|
||||
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
|
||||
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
|
||||
static const iocshFuncDef configMasterMacsCreateController = {
|
||||
"masterMacsController", 6, CreateControllerArgs};
|
||||
static void configMasterMacsCreateControllerCallFunc(const iocshArgBuf *args) {
|
||||
masterMacsCreateController(args[0].sval, args[1].sval, args[2].ival,
|
||||
args[3].dval, args[4].dval, args[5].dval);
|
||||
}
|
||||
|
||||
/*
|
||||
Same procedure as for the CreateController function, but for the axis
|
||||
itself.
|
||||
*/
|
||||
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mmacs1)",
|
||||
iocshArgString};
|
||||
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
|
||||
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
|
||||
&CreateAxisArg1};
|
||||
static const iocshFuncDef configMasterMacsCreateAxis = {"masterMacsAxis", 2,
|
||||
CreateAxisArgs};
|
||||
static void configMasterMacsCreateAxisCallFunc(const iocshArgBuf *args) {
|
||||
masterMacsCreateAxis(args[0].sval, args[1].ival);
|
||||
}
|
||||
|
||||
// This function is made known to EPICS in masterMacs.dbd and is called by EPICS
|
||||
// in order to register both functions in the IOC shell
|
||||
static void masterMacsRegister(void) {
|
||||
iocshRegister(&configMasterMacsCreateController,
|
||||
configMasterMacsCreateControllerCallFunc);
|
||||
iocshRegister(&configMasterMacsCreateAxis,
|
||||
configMasterMacsCreateAxisCallFunc);
|
||||
}
|
||||
epicsExportRegistrar(masterMacsRegister);
|
||||
|
||||
#endif
|
||||
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user