4 Commits
1.4.0 ... 1.5.0

Author SHA1 Message Date
25286652d5 Moved version definition in Makefile
Some checks failed
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Successful in 7s
The expected version can now be set in the Makefile via USR_CXXFLAGS.
Additionally, the README.md has received documentation regarding the
version check. Lastly, the version check can now be disabled by omitting
the flags or setting one of them to a negative value.
2025-08-22 13:18:43 +02:00
27f7cc8602 Added version check prototype
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 8s
2025-08-22 08:46:30 +02:00
21a73717a5 Fixed handshake detection and reset bugs
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 7s
2025-08-14 17:16:14 +02:00
2cbb4f9028 Forgot to add masterMacs.db to Makefile
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 7s
2025-08-14 14:21:05 +02:00
5 changed files with 179 additions and 61 deletions

View File

@@ -23,9 +23,14 @@ SOURCES += src/masterMacsController.cpp
# Store the record files
TEMPLATES += sinqMotor/db/asynRecord.db
TEMPLATES += sinqMotor/db/sinqMotor.db
TEMPLATES += db/masterMacs.db
# This file registers the motor-specific functions in the IOC shell.
DBDS += sinqMotor/src/sinqMotor.dbd
DBDS += src/masterMacs.dbd
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Wextra -Werror
# These flags define the expected firmware version. See README.md, section
# "Firmware version checking" for details.
USR_CXXFLAGS += -DFIRMWARE_MAJOR_VERSION=2 -DFIRMWARE_MINOR_VERSION=2

View File

@@ -66,6 +66,32 @@ dbLoadRecords("$(masterMacs_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PO
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
### Firmware version checking
This driver expects a certain version of the firmware running on the controller itself.
This is checked at IOC startup by reading the version directly from the hardware.
If the firmware version is incompatible to the driver, the IOC will be shut down.
If the firmware version cannot be read (e.g. because the variable used to do so
does not exist yet on old firmware versions), the firmware is assumed to be compatible
to the driver.
The version check is separated into a check of the major and the minor firmware
version against expected values. The firmware is seen as compatible if the following conditions hold:
- Read-out major version == Expected major version
- Read-out read major version >= Expected minor version
The expected versions are defined via compiler flags in `Makefile`:
```
USR_CXXFLAGS += -DFIRMWARE_MAJOR_VERSION=1 -DFIRMWARE_MINOR_VERSION=0
```
Be aware that these flags are only used to compile C++-files (.cpp, .cxx) and not
C-files (.c). For C-files, the Makefile variable `USR_CFLAGS` must be used.
In order to disable the checks, the flags can be set to -1 or just be removed
entirely. If one of the flags is not given, both the major and the minor version
checks are deactivated.
### How to build it
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.

View File

@@ -357,7 +357,10 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
if (timedOut) {
setAxisParamChecked(this, motorMessageText,
"Timed out while waiting for a handshake");
"Timed out while waiting for a handshake. "
"Please call the support.");
poll_status = asynError;
}
pC_->read(axisNo_, 86, response);
@@ -377,14 +380,10 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
pMasterMacsA_->waitForHandshake = false;
pMasterMacsA_->targetReachedUninitialized = false;
} else {
// Still waiting for the handshake - try again in the next busy
// poll. This is already part of the movement procedure.
*moving = true;
setAxisParamChecked(this, motorStatusMoving, *moving);
setAxisParamChecked(this, motorStatusDone, !(*moving));
return asynSuccess;
return poll_status;
}
}
@@ -403,6 +402,9 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
return rw_status;
}
// If we wait for a handshake, but the motor was moving in its last poll
// cycle and has reached its target, it is not moving. Otherwise it is
// considered moving, even if we're still waiting for the handshake.
if (pMasterMacsA_->targetReachedUninitialized) {
*moving = false;
} else {
@@ -679,7 +681,6 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
if (pl_status != asynSuccess) {
return pl_status;
}
return poll_status;
}
@@ -805,8 +806,8 @@ asynStatus masterMacsAxis::doReset() {
asynStatus status = asynSuccess;
// Reset the controller ("node reset")
status = pC_->write(axisNo_, 16, "");
// Reset any errors
status = pC_->write(axisNo_, 17, "");
if (status != asynSuccess) {
setAxisParamChecked(this, motorStatusProblem, true);
}
@@ -822,9 +823,9 @@ asynStatus masterMacsAxis::nodeReset() {
asynStatus status = asynSuccess;
// Reset any errors in the controller. Since the node reset results in a
// Reset the controller ("node reset"). Since the node reset results in a
// power cycle, we use the corresponding timeout.
status = pC_->write(axisNo_, 17, "", PowerCycleTimeout);
status = pC_->write(axisNo_, 16, "", PowerCycleTimeout);
if (status != asynSuccess) {
setAxisParamChecked(this, motorStatusProblem, true);
}

View File

@@ -12,6 +12,28 @@
#include <string>
#include <unistd.h>
/*
These functions are used to read out the compiler flags defining the major and
minor versions. See README.md, section "Firmware version checking" for
details. If these flags are not given, a default value of -1 is used, which
disables the version checks (it suffices to have one of these at -1 to disable
both major and minor version check)
*/
constexpr int firmware_major_version() {
#ifdef FIRMWARE_MAJOR_VERSION
return FIRMWARE_MAJOR_VERSION;
#else
return -1;
#endif
}
constexpr int firmware_minor_version() {
#ifdef FIRMWARE_MINOR_VERSION
return FIRMWARE_MINOR_VERSION;
#else
return -1;
#endif
}
struct masterMacsControllerImpl {
double comTimeout;
@@ -65,6 +87,7 @@ masterMacsController::masterMacsController(const char *portName,
// Initialization of local variables
asynStatus status = asynSuccess;
char response[MAXBUF_] = {0};
pMasterMacsC_ =
std::make_unique<masterMacsControllerImpl>((masterMacsControllerImpl){
@@ -116,6 +139,60 @@ masterMacsController::masterMacsController(const char *portName,
pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort());
exit(-1);
}
// =========================================================================
if (firmware_major_version() >= 0 && firmware_minor_version() >= 0) {
// Check the firmware version according to the conditions outlined in
// README.md
status = read(0, 99, response);
if (status == asynSuccess) {
// Just interpret the version if the variable already exists
double versionRaw = 0.0;
int nvals = sscanf(response, "%lf", &versionRaw);
if (nvals == 1 && versionRaw != 0.0) {
// Discard decimal part
long long versionInt = (long long)versionRaw;
// Extract bugfix (last 3 digits)
// Currently not used, just here for completions sake
// int bugfix = versionInt % 1000;
versionInt /= 1000;
// Extract minor (next 3 digits)
int minor = versionInt % 1000;
versionInt /= 1000;
// Remaining is major
int major = (int)versionInt;
// Compare to target values
if (firmware_major_version() != major ||
firmware_minor_version() > minor) {
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR "
"(Incorrect "
"version number of firmware: Expected major "
"version equal "
"to %d, got %d. Expected minor version equal to "
"or larger "
"than %d, got %d).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
firmware_major_version(), major,
firmware_minor_version(), minor);
exit(-1);
}
}
} else {
asynPrint(
this->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nCould not read firmware "
"version\n",
portName, __PRETTY_FUNCTION__, __LINE__);
}
}
}
/*
@@ -199,12 +276,6 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
comTimeout = pMasterMacsC_->comTimeout;
}
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) {
// We already did the error logging directly in getAxis
return asynError;
}
// Build the full command depending on the inputs to this function
if (isRead) {
snprintf(fullCommand, MAXBUF_ - 1, "%dR%02d\x0D", axisNo, tcpCmd);
@@ -286,32 +357,39 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
}
// Log the overall status (communication successfull or not)
if (status == asynSuccess) {
setAxisParamChecked(axis, motorStatusCommsError, false);
} else {
if (axisNo != 0) {
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) {
// We already did the error logging directly in getAxis
return asynError;
}
/*
Since the communication failed, there is the possibility that the
controller is not connected at all to the network. In that case, we
cannot be sure that the information read out in the init method of the
axis is still up-to-date the next time we get a connection. Therefore,
an info flag is set which the axis object can use at the start of its
poll method to try to initialize itself.
*/
axis->setNeedInit(true);
/*
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
*/
getAxisParamChecked(axis, motorStatusProblem, &motorStatusProblem);
if (motorStatusProblem == 0) {
setAxisParamChecked(axis, motorMessageText, drvMessageText);
setAxisParamChecked(axis, motorStatusProblem, true);
if (status == asynSuccess) {
setAxisParamChecked(axis, motorStatusCommsError, false);
} else {
/*
Since the communication failed, there is the possibility that the
controller is not connected at all to the network. In that case, we
cannot be sure that the information read out in the init method of
the axis is still up-to-date the next time we get a connection.
Therefore, an info flag is set which the axis object can use at the
start of its poll method to try to initialize itself.
*/
axis->setNeedInit(true);
/*
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
*/
getAxisParamChecked(axis, motorStatusProblem, &motorStatusProblem);
if (motorStatusProblem == 0) {
setAxisParamChecked(axis, motorMessageText, drvMessageText);
setAxisParamChecked(axis, motorStatusProblem, true);
setAxisParamChecked(axis, motorStatusCommsError, false);
}
}
}
@@ -334,7 +412,7 @@ asynStatus masterMacsController::parseResponse(
bool responseValid = false;
int responseStart = 0;
asynStatus status = asynSuccess;
int prevConnected = 0;
int prevConnected = 1;
char printableCommand[MAXBUF_] = {0};
char printableResponse[MAXBUF_] = {0};
@@ -342,12 +420,14 @@ asynStatus masterMacsController::parseResponse(
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) {
if (axisNo != 0 && axis == nullptr) {
return asynError;
}
// Was the motor previously connected?
getAxisParamChecked(axis, motorConnected, &prevConnected);
if (axis != nullptr) {
getAxisParamChecked(axis, motorConnected, &prevConnected);
}
// We don't use strlen here since the C string terminator 0x00
// occurs in the middle of the char array.
@@ -371,14 +451,17 @@ asynStatus masterMacsController::parseResponse(
"connected.\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(axis, motorConnected, true);
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return status;
if (axis != nullptr) {
setAxisParamChecked(axis, motorConnected, true);
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__,
__LINE__);
return status;
}
}
}
@@ -399,14 +482,17 @@ asynStatus masterMacsController::parseResponse(
"disconnected.\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(axis, motorConnected, false);
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return status;
if (axis != nullptr) {
setAxisParamChecked(axis, motorConnected, false);
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__,
__LINE__);
return status;
}
}
}
break;