Compare commits

...

20 Commits

Author SHA1 Message Date
mathis_s 69cad69c6f Updated sinqMotor
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Failing after 8s
2026-03-10 13:22:14 +01:00
mathis_s 703519154a Added homing check to driver 2026-03-10 13:21:34 +01:00
mathis_s 6479642f47 Updated sinqMotor
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Failing after 8s
2026-03-09 13:04:47 +01:00
mathis_s 8ef3e3a381 Added param callback 2026-03-09 11:56:06 +01:00
mathis_s 58b0e9fc77 Updated sinqMotor 2026-03-09 11:37:02 +01:00
mathis_s f0fdedfd5a Updated error message 2026-03-06 14:26:13 +01:00
mathis_s e7ce44fdb6 Test version for the homing check
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Failing after 8s
2026-03-06 11:27:25 +01:00
mathis_s 08ce0999a3 Updated underlying version of motorBase to receive the RVEL bugfix
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 6s
2026-02-12 12:05:34 +01:00
mathis_s 1d662ecd43 Use safe limit setter function from sinqMotor
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 6s
2026-02-11 11:08:46 +01:00
mathis_s bea68c807b Updated to new sinqMotor 1.6.0 version 2026-02-11 11:07:28 +01:00
mathis_s 01a04d3b24 Fixed of-by-one error
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 8s
2026-02-03 14:05:12 +01:00
mathis_s 76cc48a49c Reverted back to sinqMotor 1.5.7
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Failing after 7s
2026-02-03 13:44:21 +01:00
soederqvist_a a9f623ba1d Merge pull request 'Document how to do reset when having HW error' (#1) from fix_hw_error_doc into main
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 7s
Reviewed-on: #1
2026-02-03 10:07:56 +01:00
mathis_s 791e74f82f Expanded a bit more on the FAQ
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 7s
2026-02-03 10:02:54 +01:00
soederqvist_a 2dc10fe869 Document how to do reset when having HW error
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 7s
2026-02-03 09:49:36 +01:00
mathis_s e10aaf978e Removed unneccessary paramLib callback which caused flickering
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 7s
2026-01-23 13:21:43 +01:00
mathis_s 4159ef50bc Updated to correct version of sinqMotor 2026-01-23 11:57:40 +01:00
mathis_s d168453329 Removed reference in gitmodules
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 3s
2026-01-23 11:55:34 +01:00
mathis_s 1ea1bd8db0 Remove sinqMotor submodule completely
Test And Build / Lint (push) Failing after 3s
Test And Build / Build (push) Failing after 3s
2026-01-23 11:53:15 +01:00
mathis_s a6979d303b moved disconnect handling to sinqMotor 2026-01-22 09:47:36 +01:00
7 changed files with 291 additions and 217 deletions
-3
View File
@@ -1,6 +1,3 @@
[submodule "sinqmotor"]
path = sinqmotor
url = https://gitea.psi.ch/lin-epics-modules/sinqmotor
[submodule "sinqMotor"]
path = sinqMotor
url = https://gitea.psi.ch/lin-epics-modules/sinqMotor
+1 -1
View File
@@ -10,7 +10,7 @@ ARCH_FILTER=RHEL%
REQUIRED+=motorBase
# Specify the version of motorBase we want to build against
motorBase_VERSION=7.2.2
motorBase_VERSION=7.3.2
# These headers allow to depend on this library for derived drivers.
HEADERS += src/pmacAsynIPPort.h
+36
View File
@@ -94,3 +94,39 @@ repository is checked out AND the change is commited (`git status` shows no
non-committed changes). Please see the section "Usage as static dependency" in
https://gitea.psi.ch/lin-epics-modules/sinqMotor/src/branch/main/README.md for
more details.
## FAQ
### Axis hardware error at startup
If at IOC startup you are met with the following errors:
```
2026/02/02 16:56:49.470 Controller "turboPmac1", axis 3 => asynStatus turboPmacAxis::handleError(int, char*, int), line 735
Driver hardware error triggered.
2026/02/02 16:56:49.485 Controller "turboPmac1", axis 7 => asynStatus turboPmacAxis::handleError(int, char*, int), line 735
Driver hardware error triggered.
2026/02/02 16:56:49.492 Controller "turboPmac1", axis 8 => asynStatus turboPmacAxis::handleError(int, char*, int), line 735
Driver hardware error triggered.
```
You may need to reset to do a axis reset. This can be done with
`utils/writeRead.py`. The following example does so for axis 3.
Confirm the hardware error:
```
utils/writeRead.py 172.28.87.24:1025 P0301 # 03 specifies axis 3, 01 is the command to read out the error.
# If it returns 13 it's a hardware error.
# For the full list of errors see TurboPMAC_manual.pdf or src/turboPmacAxis.cpp, function "handleError".
```
Reset the axis:
```
utils/writeRead.py 172.28.87.24:1025 P0301=0
```
Check if the error has appeared again:
```
utils/writeRead.py 172.28.87.24:1025 P0301
```
If the console output is not 0, the error has been deleted, but appeared
immediately again. In this case, the error cannot be reset remotely.
If it is an error such as 10 (limit switches hit), the motor needs to be
moved away from the limits, this resets the error automatically.
Other errors like 13 represent an actual issue on the hardware which
needs to be resolved by the electronics motion engineers.
Submodule
+1
Submodule sinqMotor added at 86e6ab1fdd
+218 -183
View File
@@ -91,9 +91,9 @@ turboPmacAxis::turboPmacAxis(turboPmacController *pC, int axisNo,
}
// Even though this happens already in sinqAxis, a default value for
// motorMessageText is set here again, because apparently the sinqAxis
// motorErrorMessage is set here again, because apparently the sinqAxis
// constructor is not run before the string is accessed?
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 "
@@ -140,6 +140,9 @@ asynStatus turboPmacAxis::init() {
// Deadband in counts. Can be a fraction of a count, hence double.
double deadband_counts = 0.0;
// Read from the controller if the motor has been homed before
int hasBeenHomed = 0;
// Offset time for the movement watchdog caused by the air cushions in
// milliseconds.
int acoDelay = 0.0;
@@ -176,25 +179,48 @@ asynStatus turboPmacAxis::init() {
Ixx65.
*/
snprintf(command, sizeof(command),
"Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22 Q%2.2d00 I%2.2d65",
axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_);
status = pC_->writeRead(axisNo_, command, response, 7);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nCould not communicate "
"with controller during IOC initialization. Check if you used "
"\"pmacAsynIPPortConfigure\" instead of the standard "
"\"drvAsynIPPortConfigure\" function in the .cmd file in order to "
"create the port driver.\n",
pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__);
pTurboPmacA_->needInit = true;
}
nvals = sscanf(response, "%lf %lf %lf %lf %d %lf %lf", &motorPos,
&motorVmax, &motorVelocity, &motorAccel, &acoDelay,
&counts_to_eu, &deadband_counts);
"Q%2.2d10 Q%2.2d03 Q%2.2d04 Q%2.2d06 P%2.2d22 Q%2.2d00 I%2.2d65 "
"M%2.2d45",
axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_, axisNo_,
axisNo_);
status = pC_->writeRead(axisNo_, command, response, 8);
// The acoDelay is given in milliseconds -> Convert to seconds, rounded up
msgPrintControlKey keyDisconnected = msgPrintControlKey(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, 1);
if (status != asynSuccess) {
if (pC_->getMsgPrintControl().shouldBePrinted(keyDisconnected, true,
pC_->pasynUser())) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nCould not "
"communicate with controller during IOC initialization (error "
"%s). Did you use \"drvAsynIPPortConfigure\" instead of "
"\"pmacAsynIPPortConfigure\" in the .cmd file to create the "
"port driver?\n",
pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
}
pTurboPmacA_->needInit = true;
return status;
}
// No error has been detected -> Reset the error count
if (status == asynSuccess) {
pC_->getMsgPrintControl().resetCount(keyDisconnected, pC_->pasynUser());
}
nvals = sscanf(response, "%lf %lf %lf %lf %d %lf %lf %d", &motorPos,
&motorVmax, &motorVelocity, &motorAccel, &acoDelay,
&counts_to_eu, &deadband_counts, &hasBeenHomed);
if (nvals != 8) {
return pC_->couldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// The acoDelay is given in milliseconds -> Convert to seconds, rounded
// up
setOffsetMovTimeout(std::ceil(acoDelay / 1000.0));
// The PMAC electronic specifies the acceleration in m/s^2. Since we
@@ -202,11 +228,6 @@ asynStatus turboPmacAxis::init() {
// here to mm/s^2.
motorAccel = motorAccel * 1000;
if (nvals != 8) {
return pC_->couldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Store these values in the parameter library
status = setMotorPosition(motorPos);
if (status != asynSuccess) {
@@ -227,18 +248,18 @@ asynStatus turboPmacAxis::init() {
}
/*
Check if the new target position is within the range current position +/-
deadband. If that is the case, no movement command should be sent. This
functionality is implemented within the motor record itself, we just need to
populate the field SPDP (see record PushSPDB2Field in db/turboPmac.db)
Check if the new target position is within the range current position
+/- deadband. If that is the case, no movement command should be sent.
This functionality is implemented within the motor record itself, we
just need to populate the field SPDP (see record PushSPDB2Field in
db/turboPmac.db)
First, the deadband is read out (Ixx65, see Turbo PMAC User Manual, p.
160). This value then needs to be converted into counts (by dividing it by
16). After words, the deadband in counts then need to be converted into
engineering units. For this, the scaling factor Qxx10 needs to be applied
(see SINQ_2G_MCU_SW_manual_rev7, p. 17):
[Qxx10] = engineering units (eu) / counts
deadband_eu = Qxx10 * Ixx65 / 16
160). This value then needs to be converted into counts (by dividing it
by 16). After words, the deadband in counts then need to be converted
into engineering units. For this, the scaling factor Qxx10 needs to be
applied (see SINQ_2G_MCU_SW_manual_rev7, p. 17): [Qxx10] = engineering
units (eu) / counts deadband_eu = Qxx10 * Ixx65 / 16
The values of Qxx10 and Ixx65 are read out during initialization and are
assumed to not change during operation.
@@ -249,14 +270,15 @@ asynStatus turboPmacAxis::init() {
// Update the parameter library immediately
status = callParamCallbacks();
if (status != asynSuccess) {
// If we can't communicate with the parameter library, it doesn't make
// sense to try and upstream this to the user -> Just log the error
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\ncallParamCallbacks "
"failed with %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
// If we can't communicate with the parameter library, it doesn't
// make sense to try and upstream this to the user -> Just log the
// error
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\ncallParamCallbacks "
"failed with %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
return status;
}
@@ -265,6 +287,9 @@ asynStatus turboPmacAxis::init() {
pTurboPmacA_->needInit = false;
}
// Use the homing status reported by the controller
setAxisParamChecked(this, motorStatusHomed, hasBeenHomed);
return status;
}
@@ -322,7 +347,7 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
}
if (timedOut) {
setAxisParamChecked(this, motorMessageText,
setAxisParamChecked(this, motorErrorMessage,
"Timed out while waiting for a handshake");
pTurboPmacA_->waitForHandshake = false;
}
@@ -341,13 +366,13 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
}
if (error != 0) {
// If an error occurred while waiting for the handshake, abort the
// waiting procedure and continue with the poll in order to handle
// the error.
// If an error occurred while waiting for the handshake, abort
// the waiting procedure and continue with the poll in order to
// handle the error.
pTurboPmacA_->waitForHandshake = false;
} else if (handshakePerformed == 1) {
// Handshake has been performed successfully -> Continue with the
// poll
// Handshake has been performed successfully -> Continue with
// the poll
pTurboPmacA_->waitForHandshake = false;
} else {
// Still waiting for the handshake - try again in the next busy
@@ -387,15 +412,16 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
/*
The axis limits are set as: ({[]})
where [] are the positive and negative limits set in EPICS/NICOS, {} are the
software limits set on the MCU and () are the hardware limit switches. In
other words, the EPICS/NICOS limits should be stricter than the software
limits on the MCU which in turn should be stricter than the hardware limit
switches. For example, if the hardware limit switches are at [-10, 10], the
software limits could be at [-9, 9] and the EPICS / NICOS limits could be at
where [] are the positive and negative limits set in EPICS/NICOS, {} are
the software limits set on the MCU and () are the hardware limit
switches. In other words, the EPICS/NICOS limits should be stricter than
the software limits on the MCU which in turn should be stricter than the
hardware limit switches. For example, if the hardware limit switches are
at [-10, 10], the software limits could be at [-9, 9] and the EPICS /
NICOS limits could be at
[-8, 8]. Therefore, we cannot use the software limits read from the MCU
directly, but need to shrink them a bit. In this case, we're shrinking them
by limitsOffset on both sides.
directly, but need to shrink them a bit. In this case, we're shrinking
them by limitsOffset on both sides.
*/
getAxisParamChecked(this, motorLimitsOffset, &limitsOffset);
highLimit = highLimit - limitsOffset;
@@ -411,8 +437,8 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
(axStatus != -3 && axStatus != -5));
}
// Create the unique callsite identifier manually so it can be used later in
// the shouldBePrinted calls.
// Create the unique callsite identifier manually so it can be used
// later in the shouldBePrinted calls.
msgPrintControlKey keyStatus = msgPrintControlKey(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
bool resetCountStatus = true;
@@ -433,15 +459,15 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
if (pC_->getMsgPrintControl().shouldBePrinted(keyStatus, true,
pC_->pasynUser())) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nEmergency stop "
"activated.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nEmergency stop "
"activated.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
resetCountStatus = false;
setAxisParamChecked(this, motorMessageText, "Emergency stop");
setAxisParamChecked(this, motorErrorMessage, "Emergency stop");
break;
case -3:
// Disabled
@@ -508,8 +534,8 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
*moving = false;
break;
case 15:
// Move is interrupted, but the motor is not ready to receive another
// move command. Therefore it is treated as still moving.
// Move is interrupted, but the motor is not ready to receive
// another move command. Therefore it is treated as still moving.
*moving = true;
break;
case 16:
@@ -534,7 +560,7 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
userMessage, sizeof(userMessage),
"Reached unknown state P%2.2d00 = %d. Please call the support.",
axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage);
setAxisParamChecked(this, motorErrorMessage, userMessage);
}
if (resetCountStatus) {
@@ -564,8 +590,10 @@ asynStatus turboPmacAxis::doPoll(bool *moving) {
getAxisParamChecked(this, limFromHardware, &limFromHardware);
if (limFromHardware != 0) {
setAxisParamChecked(this, motorHighLimitFromDriver, highLimit);
setAxisParamChecked(this, motorLowLimitFromDriver, lowLimit);
status = setLimits(highLimit, lowLimit);
if (status != asynSuccess) {
return status;
}
}
status = setMotorPosition(currentPosition);
@@ -596,7 +624,7 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
setAxisParamChecked(this, motorMessageText,
setAxisParamChecked(this, motorErrorMessage,
"Target position would exceed software limits");
break;
case 5:
@@ -604,15 +632,15 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is "
"still moving, but received another move command. EPICS "
"should prevent this, check if *moving is set correctly.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is "
"still moving, but received another move command. EPICS "
"should prevent this, check if *moving is set "
"correctly.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
setAxisParamChecked(this, motorMessageText,
setAxisParamChecked(this, motorErrorMessage,
"Axis received move command while it is still "
"moving. Please call the support.");
break;
@@ -623,27 +651,28 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nLost feedback "
"from auxiliary device during movement (P%2.2d01 = %d).%s\n",
"from auxiliary device during movement (P%2.2d01 = "
"%d).%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo_,
error, pC_->getMsgPrintControl().getSuffix());
}
snprintf(
userMessage, sizeUserMessage,
"Lost feedback from auxiliary device during movement (P%2.2d01 = "
"%d). Please call the support.",
axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage);
snprintf(userMessage, sizeUserMessage,
"Lost feedback from auxiliary device during movement "
"(P%2.2d01 = "
"%d). Please call the support.",
axisNo_, error);
setAxisParamChecked(this, motorErrorMessage, userMessage);
break;
case 9:
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nNo feedback from "
"auxiliary device before movement (P%2.2d01 = %d).%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo_,
error, pC_->getMsgPrintControl().getSuffix());
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nNo feedback "
"from "
"auxiliary device before movement (P%2.2d01 = %d).%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axisNo_, error, pC_->getMsgPrintControl().getSuffix());
}
snprintf(
@@ -651,14 +680,14 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
"No feedback from auxiliary device before movement (P%2.2d01 = "
"%d). Please call the support.",
axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage);
setAxisParamChecked(this, motorErrorMessage, userMessage);
break;
case 10:
/*
Software limits of the controller have been hit. Since the EPICS limits
are derived from the software limits and are a little bit smaller, this
error case can only happen if either the axis has an incremental encoder
which is not properly homed or if a bug occured.
Software limits of the controller have been hit. Since the EPICS
limits are derived from the software limits and are a little bit
smaller, this error case can only happen if either the axis has an
incremental encoder which is not properly homed or if a bug occured.
*/
if (pC_->getMsgPrintControl().shouldBePrinted(
@@ -677,7 +706,7 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
"the SPS for errors (if available). "
"Otherwise please call the support.",
axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage);
setAxisParamChecked(this, motorErrorMessage, userMessage);
break;
case 11:
// Following error
@@ -685,19 +714,19 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, true,
pC_->pasynUser())) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMaximum allowed "
"following error exceeded.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMaximum "
"allowed "
"following error exceeded.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
snprintf(userMessage, sizeUserMessage,
"Maximum allowed following error exceeded (P%2.2d01 = %d). "
"Check if movement range is blocked. "
"Otherwise please call the support.",
axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage);
setAxisParamChecked(this, motorErrorMessage, userMessage);
break;
case 12:
@@ -715,7 +744,7 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
"for errors (if available). Otherwise please call "
"the support.",
axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage);
setAxisParamChecked(this, motorErrorMessage, userMessage);
break;
case 13:
@@ -730,11 +759,11 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
snprintf(
userMessage, sizeUserMessage,
"Driver hardware error (P%2.2d01 = 13). Please call the support.",
axisNo_);
setAxisParamChecked(this, motorMessageText, userMessage);
snprintf(userMessage, sizeUserMessage,
"Driver hardware error (P%2.2d01 = 13). Please call the "
"support.",
axisNo_);
setAxisParamChecked(this, motorErrorMessage, userMessage);
break;
case 14:
// EPICS should already prevent this issue in the first place,
@@ -752,7 +781,7 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
"Move command exceeds hardware limits (P%2.2d01 = %d). Please "
"call the support.",
axisNo_, error);
setAxisParamChecked(this, motorMessageText, userMessage);
setAxisParamChecked(this, motorErrorMessage, userMessage);
break;
default:
@@ -766,7 +795,7 @@ asynStatus turboPmacAxis::handleError(int error, char *userMessage,
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo_,
error, pC_->getMsgPrintControl().getSuffix());
}
setAxisParamChecked(this, motorMessageText, userMessage);
setAxisParamChecked(this, motorErrorMessage, userMessage);
break;
}
return status;
@@ -798,10 +827,10 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
if (enabled == 0) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is "
"disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynSuccess;
}
@@ -819,8 +848,8 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
getAxisParamChecked(this, motorCanSetSpeed, &motorCanSetSpeed);
// Prepend the new motor speed, if the user is allowed to set the speed.
// Mind the " " (space) before the closing "", as the command created here
// is prepended to the one down below.
// Mind the " " (space) before the closing "", as the command created
// here is prepended to the one down below.
if (motorCanSetSpeed != 0) {
snprintf(command, sizeof(command), "Q%2.2d04=%lf ", axisNo_,
motorVelocity);
@@ -833,8 +862,8 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
motorVelocity);
}
// Perform handshake, Set target position (and speed, if allowed) and start
// the move command
// Perform handshake, Set target position (and speed, if allowed) and
// start the move command
if (relative) {
snprintf(&command[writeOffset], sizeof(command) - writeOffset,
"P%2.2d23=0 Q%2.2d02=%lf M%2.2d=2", axisNo_, axisNo_,
@@ -849,18 +878,18 @@ asynStatus turboPmacAxis::doMove(double position, int relative,
status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStarting movement to "
"target position %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
motorTargetPosition);
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStarting "
"movement to "
"target position %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
motorTargetPosition);
setAxisParamChecked(this, motorStatusProblem, true);
return status;
}
// In the next poll, we will check if the handshake has been performed in a
// reasonable time
// In the next poll, we will check if the handshake has been performed
// in a reasonable time
pTurboPmacA_->waitForHandshake = true;
pTurboPmacA_->timeAtHandshake = time(NULL);
@@ -889,25 +918,25 @@ asynStatus turboPmacAxis::stop(double acceleration) {
status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStopping the movement "
"failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStopping "
"the movement "
"failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(this, motorStatusProblem, true);
}
// Reset the driver to idle state and move out of the handshake wait loop,
// if we're currently inside it.
// Reset the driver to idle state and move out of the handshake wait
// loop, if we're currently inside it.
pTurboPmacA_->waitForHandshake = false;
/*
Stopping the motor results in a movement and further move commands have to
wait until the stopping movement is done. Therefore, we need to wait until
the poller "sees" the changed state (otherwise, we risk issuing move
commands while the motor is stopping). To ensure that at least one poll is
done, this thread (which also runs move commands) is paused for twice the
idle poll period.
Stopping the motor results in a movement and further move commands have
to wait until the stopping movement is done. Therefore, we need to wait
until the poller "sees" the changed state (otherwise, we risk issuing
move commands while the motor is stopping). To ensure that at least one
poll is done, this thread (which also runs move commands) is paused for
twice the idle poll period.
*/
unsigned int idlePollMicros =
(unsigned int)ceil(pC_->idlePollPeriod() * 1e6);
@@ -938,8 +967,8 @@ asynStatus turboPmacAxis::doReset() {
setAxisParamChecked(this, motorStatusProblem, true);
}
// Reset the driver to idle state and move out of the handshake wait loop,
// if we're currently inside it.
// Reset the driver to idle state and move out of the handshake wait
// loop, if we're currently inside it.
pTurboPmacA_->waitForHandshake = false;
// Disable the axis
@@ -1037,13 +1066,14 @@ asynStatus turboPmacAxis::readEncoderType() {
}
/*
This is not a command that can always be run when enabling a motor as it also
causes relative encoders to reread a position necessitating recalibration. We
only want it to run on absolute encoders. We also want it to be clear to
instrument scientists, that power has to be cut to the motor, in order to reread
the encoder as not all motors have breaks and they may start to move when
disabled. For that reason, we don't automatically disable the motors to run the
command and instead require that the scientists first disable the motor.
This is not a command that can always be run when enabling a motor as it
also causes relative encoders to reread a position necessitating
recalibration. We only want it to run on absolute encoders. We also want it
to be clear to instrument scientists, that power has to be cut to the motor,
in order to reread the encoder as not all motors have breaks and they may
start to move when disabled. For that reason, we don't automatically disable
the motors to run the command and instead require that the scientists first
disable the motor.
*/
asynStatus turboPmacAxis::rereadEncoder() {
char command[pC_->MAXBUF_] = {0};
@@ -1082,16 +1112,17 @@ asynStatus turboPmacAxis::rereadEncoder() {
"disabled before rereading the encoder.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(
this, motorMessageText,
this, motorErrorMessage,
"Axis must be disabled before rereading the encoder.");
return asynError;
} else {
snprintf(command, sizeof(command), "M%2.2d=15", axisNo_);
asynPrint(
pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nRereading absolute "
"encoder via command %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, command);
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nRereading "
"absolute "
"encoder via command %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
command);
pC_->writeRead(axisNo_, command, response, 0);
}
@@ -1100,8 +1131,8 @@ asynStatus turboPmacAxis::rereadEncoder() {
// it is actually finished, so we instead wait for 0.5 seconds.
usleep(500000);
// Turn off parameter as finished rereading, this will only be immediately
// noticed in the read back variable though
// Turn off parameter as finished rereading, this will only be
// immediately noticed in the read back variable though
setAxisParamChecked(this, rereadEncoderPosition, false);
return asynSuccess;
}
@@ -1121,29 +1152,30 @@ asynStatus turboPmacAxis::enable(bool on) {
/*
Continue regardless of the status returned by the poll; we just want to
find out whether the motor is currently moving or not. If the poll
function fails before it can determine that, it is assumed that the motor
is not moving.
function fails before it can determine that, it is assumed that the
motor is not moving.
*/
bool moving = false;
doPoll(&moving);
// If the axis is currently moving, it cannot be disabled. Ignore the
// command and inform the user. We check the last known status of the axis
// instead of "moving", since status -6 is also moving, but the motor can
// actually be disabled in this state!
// command and inform the user. We check the last known status of the
// axis instead of "moving", since status -6 is also moving, but the
// motor can actually be disabled in this state!
int axStatus = pTurboPmacA_->axisStatus;
if (axStatus == 1 || axStatus == 2 || axStatus == 3 || axStatus == 4 ||
axStatus == 5 || axStatus == 6 || axStatus == 7 || axStatus == 8 ||
axStatus == 9 || axStatus == 10 || axStatus == 11 || axStatus == 12 ||
axStatus == 13 || axStatus == 15 || axStatus == 16) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is not "
"idle (status %d) and can therefore not be enabled / disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axStatus);
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is not "
"idle (status %d) and can therefore not be enabled / "
"disabled.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axStatus);
setAxisParamChecked(this, motorMessageText,
setAxisParamChecked(this, motorErrorMessage,
"Axis cannot be disabled while it is moving.");
pTurboPmacA_->enableDisable = false;
return asynError;
@@ -1152,11 +1184,11 @@ asynStatus turboPmacAxis::enable(bool on) {
// Axis is already enabled / disabled and a new enable / disable command
// was sent => Do nothing
if ((axStatus != -3) == on) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_WARNING,
"Controller \"%s\", axis %d => %s, line %d\nAxis is already %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
on ? "enabled" : "disabled");
asynPrint(pC_->pasynUser(), ASYN_TRACE_WARNING,
"Controller \"%s\", axis %d => %s, line %d\nAxis is "
"already %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
on ? "enabled" : "disabled");
return asynSuccess;
}
@@ -1213,7 +1245,9 @@ asynStatus turboPmacAxis::enable(bool on) {
// Enabling / disabling procedure is completed (successfully)
pTurboPmacA_->enableDisable = false;
return asynSuccess;
// Reinitialize the motor
return init();
}
}
@@ -1228,7 +1262,7 @@ asynStatus turboPmacAxis::enable(bool on) {
// Output message to user
snprintf(command, sizeof(command), "Failed to %s within %d seconds",
on ? "enable" : "disable", timeout_enable_disable);
setAxisParamChecked(this, motorMessageText, command);
setAxisParamChecked(this, motorErrorMessage, command);
// Enabling / disabling procedure failed
pTurboPmacA_->enableDisable = false;
@@ -1305,8 +1339,8 @@ asynStatus turboPmacCreateAxis(const char *portName, int axis) {
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.
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"
@@ -1329,14 +1363,15 @@ static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
static const iocshFuncDef configTurboPmacCreateAxis = {
"turboPmacAxis", 2, CreateAxisArgs,
"Create an instance of a turboPmac axis. The first argument is the "
"controller this axis should be attached to, the second argument is the "
"controller this axis should be attached to, the second argument is "
"the "
"axis number."};
static void configTurboPmacCreateAxisCallFunc(const iocshArgBuf *args) {
turboPmacCreateAxis(args[0].sval, args[1].ival);
}
// This function is made known to EPICS in turboPmac.dbd and is called by EPICS
// in order to register both functions in the IOC shell
// This function is made known to EPICS in turboPmac.dbd and is called by
// EPICS in order to register both functions in the IOC shell
static void turboPmacAxisRegister(void) {
iocshRegister(&configTurboPmacCreateAxis,
configTurboPmacCreateAxisCallFunc);
+4 -10
View File
@@ -206,7 +206,6 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
// Definition of local variables.
asynStatus status = asynSuccess;
asynStatus timeoutStatus = asynSuccess;
char drvMessageText[MAXBUF_] = {0};
char modResponse[MAXBUF_] = {0};
int motorStatusProblem = 0;
@@ -284,8 +283,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
getMsgPrintControl().getSuffix());
}
timeoutStatus = checkComTimeoutWatchdog(axisNo, drvMessageText,
sizeof(drvMessageText));
checkComTimeoutWatchdog(axisNo, drvMessageText, sizeof(drvMessageText));
int timeoutCounter = 0;
while (1) {
@@ -333,14 +331,10 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
axis->setNeedInit(true);
}
if (timeoutStatus == asynError) {
status = asynError;
}
// The message should only ever terminate due to reason 2
msgPrintControlKey terminateKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (eomReason != 2) {
if (status == asynSuccess && eomReason != 2) {
status = asynError;
char reasonStringified[30] = {0};
@@ -391,7 +385,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
}
msgPrintControlKey numResponsesKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (numExpectedResponses != numReceivedResponses) {
if (status == asynSuccess && numExpectedResponses != numReceivedResponses) {
adjustResponseForPrint(modResponse, response, MAXBUF_);
if (getMsgPrintControl().shouldBePrinted(numResponsesKey, true,
@@ -451,7 +445,7 @@ asynStatus turboPmacController::writeRead(int axisNo, const char *command,
getAxisParamChecked(axis, motorStatusProblem, &motorStatusProblem);
if (motorStatusProblem == 0) {
setAxisParamChecked(axis, motorMessageText, drvMessageText);
setAxisParamChecked(axis, motorErrorMessage, drvMessageText);
setAxisParamChecked(axis, motorStatusProblem, true);
setAxisParamChecked(axis, motorStatusCommsError, true);
}
+31 -20
View File
@@ -9,45 +9,55 @@ Stefan Mathis, December 2024
import struct
import socket
import curses
import time
def packPmacCommand(command):
# 0x40 = VR_DOWNLOAD
# 0x40 = VR_DOWNLOAD
# 0xBF = VR_PMAC_GETRESPONSE
buf = struct.pack('BBHHH',0x40,0xBF,0,0,socket.htons(len(command)))
buf = buf + bytes(command,'utf-8')
buf = struct.pack('BBHHH', 0x40, 0xBF, 0, 0, socket.htons(len(command)))
buf = buf + command.encode('utf-8')
return buf
def readPmacReply(input):
def readPmacReply(sock):
msg = bytearray()
expectAck = True
sock.settimeout(2.0)
while True:
b = input.recv(1)
bint = int.from_bytes(b,byteorder='little')
if bint == 2 or bint == 7: #STX or BELL
try:
b = sock.recv(1)
except socket.timeout:
print('Timed out')
return None
bint = int.from_bytes(b, byteorder='little')
if bint == 2 or bint == 7: # STX or BELL
expectAck = False
continue
if expectAck and bint == 6: # ACK
if expectAck and bint == 6: # ACK
return bytes(msg)
else:
if bint == 13 and not expectAck: # CR
if bint == 13 and not expectAck: # CR
return bytes(msg)
else:
msg.append(bint)
if __name__ == "__main__":
from sys import argv
try:
addr = argv[1].split(':')
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((addr[0],int(addr[1])))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((addr[0], int(addr[1])))
if len(argv) == 3:
buf = packPmacCommand(argv[2])
s.send(buf)
reply = readPmacReply(s)
print(reply.decode('utf-8') + '\n')
if reply:
print(reply.decode('utf-8') + '\n')
else:
@@ -81,13 +91,13 @@ if __name__ == "__main__":
if ptr > 0:
ptr -= 1
stdscr.addch("\r")
stdscr.clrtoeol()
stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr])
elif c == curses.KEY_DOWN:
if ptr < len(history) - 1:
ptr += 1
stdscr.addch("\r")
stdscr.clrtoeol()
stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr])
elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'):
if history[ptr] == 'quit':
@@ -113,7 +123,7 @@ if __name__ == "__main__":
stdscr.refresh()
else:
if ptr < len(history) - 1: # Modifying previous input
if ptr < len(history) - 1: # Modifying previous input
if len(history[-1]) == 0:
history[-1] = history[ptr]
ptr = len(history) - 1
@@ -126,18 +136,20 @@ if __name__ == "__main__":
if len(history[ptr]) == 0:
continue
(y, x) = stdscr.getyx()
history[ptr] = history[ptr][0:x-4] + history[ptr][x-3:]
history[ptr] = history[ptr][0:x-4] + \
history[ptr][x-3:]
stdscr.addch("\r")
stdscr.clrtoeol()
stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr])
stdscr.move(y, x-1)
stdscr.refresh()
else:
(y, x) = stdscr.getyx()
history[ptr] = history[ptr][0:x-3] + chr(c) + history[ptr][x-3:]
history[ptr] = history[ptr][0:x-3] + \
chr(c) + history[ptr][x-3:]
stdscr.addch("\r")
stdscr.clrtoeol()
stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr])
stdscr.move(y, x+1)
stdscr.refresh()
@@ -169,4 +181,3 @@ if __name__ == "__main__":
the reponse, before being prompted to again enter a command. Type
'quit' to close prompt.
""")