Compare commits

...

7 Commits

Author SHA1 Message Date
492f459678 Adjusted error message
All checks were successful
Test And Build / Lint (push) Successful in 6s
Test And Build / Build (push) Successful in 6s
2026-03-09 13:03:05 +01:00
4cb51814b8 Added callback 2026-03-09 11:55:33 +01:00
0e6c77b456 Return success if motor needs to be homed first 2026-03-09 11:36:51 +01:00
92c264d0f6 Added error message 2026-03-06 14:25:46 +01:00
a84992b4cd Improved homing doc, fixed wrong setting of motorStatusHomed and added check to move
All checks were successful
Test And Build / Lint (push) Successful in 6s
Test And Build / Build (push) Successful in 6s
2026-03-06 11:20:33 +01:00
9fee0f3c92 Renamed error message PV (kept old name as alias for backwards compatibility) 2026-03-06 08:16:01 +01:00
7b8c4db43e Added small docstring to motorVelocity explaining an important caveat.
All checks were successful
Test And Build / Lint (push) Successful in 5s
Test And Build / Build (push) Successful in 5s
2026-02-13 09:25:27 +01:00
6 changed files with 118 additions and 49 deletions

View File

@@ -117,7 +117,7 @@ does not matter):
file "$(SINQDBPATH)"
{
pattern
{ AXIS, M, DESC, EGU, DIR, MRES, MSGTEXTSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED, ADAPTPOLL }
{ AXIS, M, DESC, EGU, DIR, MRES, ERRORMSGSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED, ADAPTPOLL }
{ 1, "lin1", "Linear motor doing whatever", mm, Pos, 0.001, 200, 1, 1.0, 1, 1 }
{ 2, "rot1", "First rotary motor", degree, Neg, 0.001, 200, 0, 1.0, 0, 1 }
{ 3, "rot2", "Second rotary motor", degree, Pos, 0.001, 200, 0, 0.0, 1, 0 }
@@ -146,7 +146,7 @@ The default values for those parameters are given for the individual records in
`db/sinqMotor.db`
- `DESC`: Description of the motor. This field is just for documentation and is
not needed for operating a motor. Defaults to the motor name.
- `MSGTEXTSIZE`: Buffer size for the motor message record in characters.
- `ERRORMSGSIZE`: Buffer size for the motor message record in characters.
Defaults to 200 characters
- `ENABLEMOVWATCHDOG`: Sets `setWatchdogEnabled` during IOC startup to the given
value. Defaults to 0.
@@ -280,9 +280,9 @@ bookkeeping tasks before and after calling `doPoll`:
message is waiting in the temporary buffer, set `motorStatusProblem` to true,
otherwise to false. If an old error message is waiting in the temporary
buffer, but `doPoll` returned `asynSuccess`, overwrite the paramLib entry for
`motorMessageText` with the old error message.
`motorErrorMessage` with the old error message.
- Run `callParamCallbacks`
- Reset `motorMessageText` AFTER updating the PVs. This makes sure that the
- Reset `motorErrorMessage` AFTER updating the PVs. This makes sure that the
error message is shown for at least one poll cycle.
- Return the status of `doPoll`
- `motorPosition`: Returns the parameter library value of the motor position,

View File

@@ -107,14 +107,15 @@ record(ao,"$(INSTR)$(M):RecResolution") {
}
# This record contains messages from the driver (usually error messages).
# The macro MSGTEXTSIZE can be used to set the maximum length of the message.
# The macro ERRORMSGSIZE can be used to set the maximum length of the message.
# if not provided, a default value of 200 is used.
# This record is coupled to the parameter library via motorMessageText -> MOTOR_MESSAGE_TEXT.
record(waveform, "$(INSTR)$(M)-MsgTxt") {
# This record is coupled to the parameter library via motorErrorMessage -> MOTOR_MESSAGE_TEXT.
record(waveform, "$(INSTR)$(M):ErrorMessage") {
alias("$(INSTR)$(M)-MsgTxt") # Old PV name, aliased for backwards compatibility
field(DTYP, "asynOctetRead")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_MESSAGE_TEXT")
field(FTVL, "CHAR")
field(NELM, "$(MSGTEXTSIZE=200)") # Should be the same as MAXBUF in the driver code
field(NELM, "$(ERRORMSGSIZE=200)") # Should be the same as MAXBUF in the driver code
field(SCAN, "I/O Intr")
}

View File

@@ -72,7 +72,7 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
Initialize the parameter library entry for the motor message text, because
it is read during the first poll before it has been written to.
*/
status = setStringParam(pC_->motorMessageText(), "");
status = setStringParam(pC_->motorErrorMessage(), "");
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
@@ -143,7 +143,7 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
exit(-1);
}
// Set the homing-related flags
// Motor is assumed to not being in a homing run at IOC startup.
status = setIntegerParam(pC_->motorStatusHome(), 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
@@ -154,7 +154,9 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
pC_->stringifyAsynStatus(status));
exit(-1);
}
status = setIntegerParam(pC_->motorStatusHomed(), 0);
// Motor is assumed to not be at the home position at IOC startup.
status = setIntegerParam(pC_->motorStatusAtHome(), 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
@@ -164,7 +166,9 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
pC_->stringifyAsynStatus(status));
exit(-1);
}
status = setIntegerParam(pC_->motorStatusAtHome(), 0);
// Motor is assumed to not have been homed in the past.
status = setIntegerParam(pC_->motorStatusHomed(), 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
@@ -232,14 +236,14 @@ asynStatus sinqAxis::forcedPoll(bool *moving) {
pSinqA_->lastPollTime = ts;
/*
If the "motorMessageText" record currently contains an error message, it
If the "motorErrorMessage" record currently contains an error message, it
should be shown for at least one poll period. To assure this, it is read out
here from the paramLib into "waitingMessage". If no new error message was
added to the parameter library at the end of the poll cycle, the
"waitingMessage" is briefly put into the paramLib again, then the PVs are
updated and then the message text is cleared again.
*/
getAxisParamChecked(this, motorMessageText,
getAxisParamChecked(this, motorErrorMessage,
static_cast<char *>(waitingMessage));
// Clear the communication
@@ -271,16 +275,16 @@ asynStatus sinqAxis::forcedPoll(bool *moving) {
If doPoll cleared the error message paramLib entry, but an old message
is still waiting, set the old message.
*/
getAxisParamChecked(this, motorMessageText,
getAxisParamChecked(this, motorErrorMessage,
static_cast<char *>(newMessage));
if (newMessage[0] == '\0') {
setAxisParamChecked(this, motorMessageText,
setAxisParamChecked(this, motorErrorMessage,
static_cast<char *>(waitingMessage));
}
setAxisParamChecked(this, motorStatusProblem, true);
} else {
// No errors are waiting -> Clear everything.
setAxisParamChecked(this, motorMessageText, "");
setAxisParamChecked(this, motorErrorMessage, "");
setAxisParamChecked(this, motorStatusProblem, false);
}
@@ -330,7 +334,7 @@ asynStatus sinqAxis::forcedPoll(bool *moving) {
Delete the error message AFTER updating the PVs so it is not there anymore
during the next poll.
*/
setAxisParamChecked(this, motorMessageText, "");
setAxisParamChecked(this, motorErrorMessage, "");
return poll_status;
}
@@ -348,9 +352,27 @@ asynStatus sinqAxis::move(double position, int relative, double minVelocity,
// Status of parameter library operations
asynStatus status = asynSuccess;
double motorRecRes = 0.0;
char encType[pC_->MAXBUF_] = {0};
int motorStatHomed = 0;
// =========================================================================
/*
Check if the motor is allowed to move: If the motor hasn't been homed in the
past and has an incremental encoder, it needs to be homed first!
*/
getAxisParamChecked(this, encoderType, &encType);
getAxisParamChecked(this, motorStatusHomed, &motorStatHomed);
if (strcmp(encType, IncrementalEncoder) == 0 && motorStatHomed == 0) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nAxis needs to be homed first.\n",
pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(this, motorErrorMessage,
"Motor needs to be homed / referenced first.");
return pC_->callParamCallbacks();
}
// Store the target position internally
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
pSinqA_->targetPosition = position * motorRecRes;
@@ -367,7 +389,6 @@ asynStatus sinqAxis::move(double position, int relative, double minVelocity,
// Since the move command was successfull, we assume that the motor has
// started its movement.
setAxisParamChecked(this, motorStatusHomed, false);
setAxisParamChecked(this, motorStatusAtHome, false);
// Needed for adaptive polling
@@ -410,7 +431,7 @@ asynStatus sinqAxis::home(double minVelocity, double maxVelocity,
} else if (status == asynError) {
// asynError means that we tried to home an absolute encoder
setAxisParamChecked(this, motorMessageText,
setAxisParamChecked(this, motorErrorMessage,
"Can't home a motor with absolute encoder");
status = assertConnected();
@@ -547,7 +568,7 @@ asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
"vmax=%lf.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
vbas, vmax);
setAxisParamChecked(this, motorMessageText,
setAxisParamChecked(this, motorErrorMessage,
"Lower speed limit must not be smaller than "
"upper speed limit. Please call the support.");
return asynError;
@@ -561,7 +582,7 @@ asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
velo, vbas, vmax);
setAxisParamChecked(
this, motorMessageText,
this, motorErrorMessage,
"Speed is not inside limits. Set a new valid speed and try "
"to move the motor. Otherwise, please call the support.");
return asynError;
@@ -704,7 +725,7 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
}
setAxisParamChecked(
this, motorMessageText,
this, motorErrorMessage,
"Exceeded expected arrival time. Check if the axis is blocked.");
setAxisParamChecked(this, motorStatusProblem, true);
} else {

View File

@@ -111,10 +111,15 @@ class HIDDEN sinqAxis : public asynMotorAxis {
* @brief Perform some standardized operations before and after the concrete
`doMove` implementation.
* Wrapper around `doMove` which calculates the (absolute) target position
and stores it in the member variable `targetPosition_`. This member variable
is e.g. used for the movement watchdog. Afterwards, it calls and returns
`doMove`.
Wrapper around `doMove` which performs the following operations:
- If the motor has an incremental encoder and the paramLib entry for
motorStatusHomed returns 0, the move command is not forwarded if the motor
has an incremental encoder.
- It calculates the (absolute) target position and stores it in the member
variable `targetPosition_`. This member variable is e.g. used for the
movement watchdog.
Afterwards, it calls and returns `doMove`.
*
* @param position Forwarded to `doMove`.
* @param relative Forwarded to `doMove`.
@@ -154,16 +159,24 @@ class HIDDEN sinqAxis : public asynMotorAxis {
within the driver.
* - `motorStatusHome_`: This flag should be set to `1` while the motor is
actively moving toward its home position and to `0` when the home position
is reached.
*
* - `motorStatusHomed_`: This flag should be set to `0` at the start of a
homing command and to 1 once the home position is reached.
*
* - `motorStatusAtHome_`: This flag is similar to `motorStatusHomed_`, but
in addition it should also be `1` when the motor is at its home position,
but wasn't actively homed in order to get there.
*
actively moving toward its home position and to `0` otherwise.
* - `motorStatusAtHome_`: This flag should be set to `0` at the start of a
homing command and to 1 once the home position is reached. This is obviously
the case right after a homing run. Corresponds to bit 7 of the MSTA field of
the motor record.
* - `motorStatusHomed_`: This flag should be set to `1` if it is known that
that the motor has been homed since the last power cycle of the controller.
There are two ways the motor record can get this information:
1) If a home run has been performed by the driver itself via
`sinqAxis::home` (this function).
2) If the controller reports that the motor has been homed in the past
(possibly before the IOC startup). This information needs to be provided by
the controller (e.g. in an "init" function). Likewise, if the driver detects
a power cycle of the controller, the driver must set this field back to `0`.
* This function performs the following operations in the given order:
*
* - Call `doHome()` and forward the parameters
@@ -178,8 +191,8 @@ class HIDDEN sinqAxis : public asynMotorAxis {
* - If `doHome()` returned anything else: Forward the status.
*
* The flags `motorStatusHome_`, `motorStatusHomed_` and
`motorStatusAtHome_` are set to their idle values (0, 1 and 1 respectively)
in the `forcedPoll())` method once the homing procedure is finished.
`motorStatusAtHome_` are set to 0, 1 and 1 respectively in the
`forcedPoll())` method once the homing procedure is finished.
*
* @param minVelocity Forwarded to `doHome`.
* @param maxVelocity Forwarded to `doHome`.

View File

@@ -76,7 +76,7 @@ struct sinqControllerImpl {
These integers are indices to paramLib entries and are populated when the
parameters are created. See the documentation in db/sinqMotor.db.
*/
int motorMessageText;
int motorErrorMessage;
int motorReset;
int motorEnable;
int motorEnableRBV;
@@ -130,7 +130,7 @@ sinqController::sinqController(const char *portName,
.timeoutEvents = {},
.maxSubsequentTimeouts = 10,
.maxSubsequentTimeoutsExceeded = false,
.motorMessageText = 0,
.motorErrorMessage = 0,
.motorReset = 0,
.motorEnable = 0,
.motorEnableRBV = 0,
@@ -180,7 +180,7 @@ sinqController::sinqController(const char *portName,
// This text is used to forward status messages to NICOS and in turn to the
// user.
status = createParam("MOTOR_MESSAGE_TEXT", asynParamOctet,
&pSinqC_->motorMessageText);
&pSinqC_->motorErrorMessage);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@@ -504,7 +504,7 @@ asynStatus sinqController::couldNotParseResponse(const char *command,
}
setAxisParamChecked(
axis, motorMessageText,
axis, motorErrorMessage,
"Could not interpret controller response. Please call the support");
setAxisParamChecked(axis, motorStatusCommsError, true);
@@ -526,7 +526,7 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
// Log the error message and try to propagate it. If propagating fails,
// there is nothing we can do here anyway.
setStringParam(motorMessageText(),
setStringParam(motorErrorMessage(),
"Accessing paramLib failed. Please call the support.");
}
@@ -597,7 +597,7 @@ asynStatus sinqController::checkComTimeoutWatchdog(sinqAxis *axis) {
asynStatus status =
checkComTimeoutWatchdog(axis->axisNo(), errorMessage, MAXBUF_);
if (status == asynError) {
setAxisParamChecked(axis, motorMessageText, errorMessage);
setAxisParamChecked(axis, motorErrorMessage, errorMessage);
}
return status;
}
@@ -645,7 +645,7 @@ asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo,
asynStatus status = checkMaxSubsequentTimeouts(timeoutNo, axis->axisNo(),
motorMessage, MAXBUF_);
if (status == asynError) {
setAxisParamChecked(axis, motorMessageText, motorMessage);
setAxisParamChecked(axis, motorErrorMessage, motorMessage);
}
return status;
}
@@ -709,7 +709,7 @@ asynStatus sinqController::setThresholdComTimeout(time_t comTimeoutWindow,
return asynSuccess;
}
int sinqController::motorMessageText() { return pSinqC_->motorMessageText; }
int sinqController::motorErrorMessage() { return pSinqC_->motorErrorMessage; }
int sinqController::motorReset() { return pSinqC_->motorReset; }
int sinqController::motorEnable() { return pSinqC_->motorEnable; }
int sinqController::motorEnableRBV() { return pSinqC_->motorEnableRBV; }

View File

@@ -101,7 +101,7 @@ class HIDDEN sinqController : public asynMotorController {
*
* If accessing the parameter library failed (return status !=
asynSuccess), calling this function writes a standardized message to both
the IOC shell and the motorMessageText PV. It then returns the input
the IOC shell and the motorErrorMessage PV. It then returns the input
status.
*
* @param status Status of the failed parameter library access
@@ -267,6 +267,19 @@ class HIDDEN sinqController : public asynMotorController {
int motorMoveVel() { return motorMoveVel_; }
int motorHome() { return motorHome_; }
int motorStop() { return motorStop_; }
/**
* @brief Index for the paramLib entry which writes the motor speed in steps
* / s into the RVEL field of the motor record.
*
* Important: Only write to this field if the motor is moving. As discussed
* here: https://github.com/epics-modules/motor/pull/236, otherwise a race
* condition might occur and the move or moveVelocity might be called with
* the readback velocity instead of that written into the VELO / JVEL
* fields.
*
* @return Index of the paramLib entry
*/
int motorVelocity() { return motorVelocity_; }
int motorVelBase() { return motorVelBase_; }
int motorAccel() { return motorAccel_; }
@@ -294,10 +307,24 @@ class HIDDEN sinqController : public asynMotorController {
int motorStatusDirection() { return motorStatusDirection_; }
int motorStatusDone() { return motorStatusDone_; }
int motorStatusHighLimit() { return motorStatusHighLimit_; }
/**
* @brief Set to 1 if the motor is currently homing and 0 otherwise.
*
* See docstring of the sinqAxis::home function.
*
* @return Index of the paramLib entry
*/
int motorStatusAtHome() { return motorStatusAtHome_; }
int motorStatusSlip() { return motorStatusSlip_; }
int motorStatusPowerOn() { return motorStatusPowerOn_; }
int motorStatusFollowingError() { return motorStatusFollowingError_; }
/**
* @brief Set to 1 if the motor is currently at its home position.
*
* See docstring of the sinqAxis::home function.
*
* @return Index of the paramLib entry
*/
int motorStatusHome() { return motorStatusHome_; }
int motorStatusHasEncoder() { return motorStatusHasEncoder_; }
int motorStatusProblem() { return motorStatusProblem_; }
@@ -305,6 +332,13 @@ class HIDDEN sinqController : public asynMotorController {
int motorStatusGainSupport() { return motorStatusGainSupport_; }
int motorStatusCommsError() { return motorStatusCommsError_; }
int motorStatusLowLimit() { return motorStatusLowLimit_; }
/**
* @brief Set to 1 if the motor has been homed in the past and 0 otherwise
*
* See docstring of the sinqAxis::home function.
*
* @return Index of the paramLib entry
*/
int motorStatusHomed() { return motorStatusHomed_; }
// Parameters for passing additional motor record information to the driver
@@ -314,7 +348,7 @@ class HIDDEN sinqController : public asynMotorController {
// Accessors for additional PVs defined in sinqController (which are hidden
// in pSinqC_)
int motorMessageText();
int motorErrorMessage();
int motorReset();
int motorEnable();
int motorEnableRBV();