Added interpose driver from DLS

Added a low-level interpose driver to allow usage of stream devices.
This commit is contained in:
2025-04-17 09:25:42 +02:00
parent 7b904e30db
commit e5a4af14ea
10 changed files with 1147 additions and 1012 deletions

View File

@@ -1,6 +1,8 @@
#include "turboPmacController.h"
#include "asynInt32SyncIO.h"
#include "asynMotorController.h"
#include "asynOctetSyncIO.h"
#include "pmacAsynIPPort.h"
#include "turboPmacAxis.h"
#include <epicsExport.h>
#include <errlog.h>
@@ -79,6 +81,16 @@ turboPmacController::turboPmacController(const char *portName,
exit(-1);
}
status = createParam("FLUSH_HARDWARE", asynParamInt32, &flushHardware_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
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
@@ -88,7 +100,8 @@ turboPmacController::turboPmacController(const char *portName,
const char *message_from_device =
"\006"; // Hex-code for ACK (acknowledge) -> Each message from the MCU
// is terminated by this value
status = pasynOctetSyncIO->setInputEos(ipPortUser_, message_from_device,
status = pasynOctetSyncIO->setInputEos(pasynOctetSyncIOipPort_,
message_from_device,
strlen(message_from_device));
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
@@ -96,7 +109,7 @@ turboPmacController::turboPmacController(const char *portName,
"(setting input EOS failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(ipPortUser_);
pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort_);
exit(-1);
}
@@ -108,7 +121,23 @@ turboPmacController::turboPmacController(const char *portName,
"with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(ipPortUser_);
pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort_);
exit(-1);
}
// =========================================================================;
/*
We try to connect to the port via the port name provided by the constructor.
If this fails, the function is terminated via exit.
*/
pasynInt32SyncIO->connect(ipPortConfigName, 0, &pasynInt32SyncIOipPort_,
NULL);
if (status != asynSuccess || pasynInt32SyncIOipPort_ == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot "
"connect to MCU controller).\n"
"Terminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__);
exit(-1);
}
}
@@ -175,83 +204,25 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
}
const size_t commandLength = strlen(command);
// NOT NEEDED ANYMORE DUE TO THE LOW LEVEL DRIVER
// /*
// The message protocol of the turboPmac used at PSI looks as follows (all
// characters immediately following each other without a newline):
// 0x40 (ASCII value of @) -> Request for download
// 0xBF (ASCII value of ¿) -> Select mode "get_response"
// 0x00 (ASCII value of 0)
// 0x00 (ASCII value of 0)
// 0x00 (ASCII value of 0)
// 0x00 (ASCII value of 0)
// 0x00 (ASCII value of 0)
// [message length in network byte order] -> Use the htons function for this
// value [Actual message] It is not necessary to append a terminator, since
// this protocol encodes the message length at the beginning. See Turbo PMAC
// User Manual, page 418 in VR_PMAC_GETRESPONSE
// x0D (ASCII value of carriage return) -> The controller needs a carriage
// return at the end of a "send" command (a command were we transmit data
// via
// =). For "request" commands (e.g. read status or position), this is not
// necessary, but it doesn't hurt either, therefore we always add a carriage
// return.
// The message has to be build manually into the buffer fullCommand, since
// it contains NULL terminators in its middle, therefore the string
// manipulation methods of C don't work.
// */
//
// const int offset = 9;
// // Positions 2 to 6 must have the value 0. Since fullCommand is
// initialized
// // as an array of zeros, we don't need to set these bits manually.
// fullCommand[0] = '\x40';
// fullCommand[1] = '\xBF';
// // The size of size_t is platform dependant (pointers-sized), while htons
// // needs an unsigned int. The byte order is then converted from host to
// // network order. The offset "+1" is for the carriage return.
// u_int16_t len = htons(static_cast<u_int16_t>(commandLength + 1));
// // Split up into the upper and the lower byte
// fullCommand[7] = (char)(len >> 8); // Shift the 8 higher bits to the
// right fullCommand[8] = (char)(len & 0xFF); // Mask the higher bits
// // Write the actual command behind the protocol
// for (size_t i = 0; i < commandLength; i++) {
// fullCommand[i + offset] = command[i];
// }
// fullCommand[offset + commandLength] = '\x0D';
// // +1 for the carriage return.
// const size_t fullComandLength = offset + commandLength + 1;
/*
We use separated write and read commands here, not the combined writeRead
method, because the latter is actually a flushWriteRead (see
https://epics.anl.gov/modules/soft/asyn/R4-14/asynDriver.pdf, p. 31) ->
Calls the flush command, then the write command, then the read command.
The flush itself reads repeatedly from the MCU until no messages are there
anymore. (The Diamond Light Source driver first send a PMAC flush command
and then does the same as the asyn flush). We don't want this behaviour.
(https://www.slac.stanford.edu/grp/lcls/controls/global/doc/epics-modules/R3-14-12/asyn/asyn-R4-18-lcls2/asyn/interfaces/asynOctetBase.c)
The writeRead command performs the following steps:
1) Flush the socket buffer on the IOC side (not the controller!)
2) Write a command to the controller
3) Read the response
If a timeout occurs during writing or reading, inform the user that we're
trying to reconnect. If the problem persists, ask them to call the support
*/
status = pasynOctetSyncIO->writeRead(
pasynOctetSyncIOipPort(), command, commandLength, response, MAXBUF_,
comTimeout_, &nbytesOut, &nbytesIn, &eomReason);
status = pasynOctetSyncIO->write(ipPortUser_, command, commandLength,
comTimeout_, &nbytesOut);
msgPrintControlKey writeKey =
msgPrintControlKey comKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (status == asynTimeout) {
if (msgPrintControl_.shouldBePrinted(writeKey, true, pasynUserSelf)) {
if (msgPrintControl_.shouldBePrinted(comKey, true, pasynUserSelf)) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nTimeout while "
@@ -268,8 +239,9 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
checkMaxSubsequentTimeouts(timeoutCounter, axis);
timeoutCounter += 1;
status = pasynOctetSyncIO->write(
ipPortUser_, command, commandLength, comTimeout_, &nbytesOut);
status = pasynOctetSyncIO->writeRead(
pasynOctetSyncIOipPort(), command, commandLength, response,
MAXBUF_, comTimeout_, &nbytesOut, &nbytesIn, &eomReason);
if (status != asynTimeout) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
@@ -279,7 +251,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
}
}
} else if (status != asynSuccess) {
if (msgPrintControl_.shouldBePrinted(writeKey, true, pasynUserSelf)) {
if (msgPrintControl_.shouldBePrinted(comKey, true, pasynUserSelf)) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError %s while "
@@ -288,57 +260,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
stringifyAsynStatus(status), msgPrintControl_.getSuffix());
}
} else {
msgPrintControl_.resetCount(writeKey, pasynUserSelf);
}
// Read the response from the MCU buffer
status = pasynOctetSyncIO->read(ipPortUser_, response, MAXBUF_, comTimeout_,
&nbytesIn, &eomReason);
msgPrintControlKey readKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (status == asynTimeout) {
if (msgPrintControl_.shouldBePrinted(readKey, true, pasynUserSelf)) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nTimeout while "
"reading from the MCU.%s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
msgPrintControl_.getSuffix());
}
// Add this event to the back of the timeout event counter
timeoutStatus = checkComTimeoutWatchdog(axisNo, drvMessageText,
sizeof(drvMessageText));
int timeoutCounter = 0;
while (1) {
checkMaxSubsequentTimeouts(timeoutCounter, axis);
timeoutCounter += 1;
status = pasynOctetSyncIO->read(ipPortUser_, response, MAXBUF_,
comTimeout_, &nbytesIn, &eomReason);
if (status != asynTimeout) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nReconnected after read timeout\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
break;
}
}
} else if (status != asynSuccess) {
if (msgPrintControl_.shouldBePrinted(readKey, true, pasynUserSelf)) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError %s while "
"reading from the controller.%s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status), msgPrintControl_.getSuffix());
}
} else {
msgPrintControl_.resetCount(readKey, pasynUserSelf);
msgPrintControl_.resetCount(comKey, pasynUserSelf);
}
if (timeoutStatus == asynError) {
@@ -352,7 +274,8 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
status = asynError;
snprintf(drvMessageText, sizeof(drvMessageText),
"Terminated message due to reason %d (should be 2).",
"Terminated message due to reason %d (should be 2). Please "
"call the support.",
eomReason);
if (msgPrintControl_.shouldBePrinted(terminateKey, true,
@@ -469,6 +392,22 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
return status;
}
asynStatus turboPmacController::doFlushHardware() {
/*
Temporarily overwrite the "reason" field with the FLUSH_HARDWARE
constant defined in pmacAsynIPPort.c. This reason is then used within
the write method of pasynInt32SyncIO to select the flush function.
*/
int temp = pasynInt32SyncIOipPort_->reason;
pasynInt32SyncIOipPort_->reason = FLUSH_HARDWARE;
asynStatus status = (asynStatus)pasynInt32SyncIO->write(
pasynInt32SyncIOipPort_, 1, comTimeout_);
// Reset the status afterwards
pasynInt32SyncIOipPort_->reason = temp;
return status;
}
asynStatus turboPmacController::writeInt32(asynUser *pasynUser,
epicsInt32 value) {
int function = pasynUser->reason;
@@ -482,6 +421,8 @@ asynStatus turboPmacController::writeInt32(asynUser *pasynUser,
return axis->rereadEncoder();
} else if (function == readConfig_) {
return axis->init();
} else if (function == flushHardware_) {
return doFlushHardware();
} else {
return sinqController::writeInt32(pasynUser, value);
}