Compare commits
17 Commits
SINQSW-107
...
master
Author | SHA1 | Date | |
---|---|---|---|
354e9d90fb | |||
deea821e3f | |||
7a46788fd5 | |||
9e77eb585c | |||
d0c009ea38 | |||
7e1fc78f76 | |||
9e0d8a4322 | |||
3cccfe930c | |||
8860d0c59f | |||
b6c38be113 | |||
b14b50c25a | |||
477ffdbc0b | |||
0a23ec8f22 | |||
eb1bb58c36 | |||
80205727c7 | |||
d44fdbf736 | |||
20e5c35d44 |
@ -9,28 +9,24 @@ AlignConsecutiveAssignments:
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
@ -44,10 +40,8 @@ AlignTrailingComments:
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowBreakBeforeNoexceptSpecifier: Never
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortCompoundRequirementOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
@ -81,8 +75,7 @@ BraceWrapping:
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAdjacentStringLiterals: true
|
||||
BreakAfterAttributes: Leave
|
||||
BreakAfterAttributes: Never
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
@ -167,7 +160,6 @@ PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakScopeResolution: 500
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
@ -185,7 +177,6 @@ RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SkipMacroDefinitionBody: false
|
||||
SortIncludes: CaseSensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: LexicographicNumeric
|
||||
@ -207,7 +198,6 @@ SpaceBeforeParensOptions:
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterPlacementOperator: true
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
@ -233,7 +223,7 @@ StatementAttributeLikeMacros:
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
|
51
.gitlab-ci.yml
Normal file
51
.gitlab-ci.yml
Normal file
@ -0,0 +1,51 @@
|
||||
default:
|
||||
image: docker.psi.ch:5000/wall_e/sinqepics:latest
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
|
||||
cppcheck:
|
||||
stage: test
|
||||
script:
|
||||
- cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 sinqEPICSApp/
|
||||
allow_failure: true # Long term this needs to be removed
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
tags:
|
||||
- docker
|
||||
|
||||
formatting:
|
||||
stage: test
|
||||
script:
|
||||
- clang-format --style=file --Werror --dry-run sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h
|
||||
allow_failure: true # Long term this needs to be removed
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
tags:
|
||||
- docker
|
||||
|
||||
# clangtidy:
|
||||
# stage: test
|
||||
# script:
|
||||
# - curl https://docker.psi.ch:5000/v2/_catalog
|
||||
# # - dnf update -y
|
||||
# # - dnf install -y clang-tools-extra
|
||||
# # - clang-tidy sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h -checks=cppcoreguidelines-*,cert-*
|
||||
# # tags:
|
||||
# # - docker
|
||||
|
||||
build_module:
|
||||
stage: build
|
||||
script:
|
||||
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile.RHEL8
|
||||
- make -f Makefile.RHEL8 install
|
||||
- cp -rT "/ioc/modules/sinq/$(ls -U /ioc/modules/sinq/ | head -1)" "./sinq-${CI_COMMIT_SHORT_SHA}"
|
||||
artifacts:
|
||||
name: "sinq-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- "sinq-${CI_COMMIT_SHORT_SHA}/*"
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
tags:
|
||||
- docker
|
@ -4,21 +4,19 @@ include /ioc/tools/driver.makefile
|
||||
MODULE=sinq
|
||||
BUILDCLASSES=Linux
|
||||
EPICS_VERSIONS=7.0.7
|
||||
ARCH_FILTER=RHEL%
|
||||
ARCH_FILTER=RHEL8%
|
||||
|
||||
# additional module dependencies
|
||||
REQUIRED+=SynApps
|
||||
REQUIRED+=stream
|
||||
REQUIRED+=scaler
|
||||
REQUIRED+=asynMotor
|
||||
|
||||
# Release version
|
||||
LIBVERSION=2024-v2
|
||||
REQUIRED+=motorBase
|
||||
|
||||
# DB files to include in the release
|
||||
TEMPLATES += sinqEPICSApp/Db/dimetix.db
|
||||
TEMPLATES += sinqEPICSApp/Db/slsvme.db
|
||||
TEMPLATES += sinqEPICSApp/Db/spsamor.db
|
||||
TEMPLATES += sinqEPICSApp/Db/el734.db
|
||||
|
||||
# DBD files to include in the release
|
||||
DBDS += sinqEPICSApp/src/sinq.dbd
|
||||
@ -32,10 +30,12 @@ SOURCES += sinqEPICSApp/src/NanotecDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/stptok.cpp
|
||||
SOURCES += sinqEPICSApp/src/PhytronDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/EuroMoveDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/pmacAsynIPPort.c
|
||||
SOURCES += sinqEPICSApp/src/pmacAxis.cpp
|
||||
SOURCES += sinqEPICSApp/src/pmacController.cpp
|
||||
# SOURCES += sinqEPICSApp/src/pmacAsynIPPort.c
|
||||
# SOURCES += sinqEPICSApp/src/pmacAxis.cpp
|
||||
# SOURCES += sinqEPICSApp/src/pmacController.cpp
|
||||
SOURCES += sinqEPICSApp/src/MasterMACSDriver.cpp
|
||||
SOURCES += sinqEPICSApp/src/C804Axis.cpp
|
||||
SOURCES += sinqEPICSApp/src/C804Controller.cpp
|
||||
|
||||
USR_CFLAGS += -Wall -Wextra # -Werror
|
||||
|
||||
|
34
sinqEPICSApp/Db/el734.db
Normal file
34
sinqEPICSApp/Db/el734.db
Normal file
@ -0,0 +1,34 @@
|
||||
record(motor,"$(P)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC)")
|
||||
field(DTYP,"$(DTYP)")
|
||||
field(DIR,"$(DIR)")
|
||||
field(VELO,"$(VELO)")
|
||||
field(HVEL,"$(VELO)")
|
||||
field(VBAS,"$(VELO)")
|
||||
field(VMAX, "${VMAX}")
|
||||
field(ACCL,"$(ACCL)")
|
||||
field(BDST,"$(BDST)")
|
||||
field(BVEL,"$(BVEL)")
|
||||
field(BACC,"$(BACC)")
|
||||
field(OUT,"@asyn($(PORT),$(ADDR))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(PREC,"$(PREC)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(DHLM,"$(DHLM)")
|
||||
field(DLLM,"$(DLLM)")
|
||||
field(INIT,"$(INIT)")
|
||||
field(PINI, "NO")
|
||||
field(TWV,"1")
|
||||
field(RTRY,"0")
|
||||
}
|
||||
|
||||
# The message text
|
||||
record(waveform, "$(P)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(PORT),$(N),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
477
sinqEPICSApp/src/C804Axis.cpp
Normal file
477
sinqEPICSApp/src/C804Axis.cpp
Normal file
@ -0,0 +1,477 @@
|
||||
#include "C804Axis.h"
|
||||
#include "C804Controller.h"
|
||||
#include <cmath>
|
||||
#include <errlog.h>
|
||||
#include <limits>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
C804Axis::C804Axis(C804Controller *pC, int axisNo)
|
||||
: SINQAxis(pC, axisNo), pC_(pC) {
|
||||
/*
|
||||
The superclass constructor SINQAxis calls in turn its superclass constructor
|
||||
asynMotorAxis. In the latter, a pointer to the constructed object this is
|
||||
stored inside the array pAxes_:
|
||||
|
||||
pC->pAxes_[axisNo] = this;
|
||||
|
||||
Therefore, the axes are managed by the controller pC. See C804Controller.cpp
|
||||
for further explanation. If axisNo is out of bounds, asynMotorAxis prints an
|
||||
error (see
|
||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp,
|
||||
line 40). However, we want the IOC creation to stop completely, since this
|
||||
is a configuration error.
|
||||
*/
|
||||
if (axisNo >= pC->numAxes_) {
|
||||
exit(-1);
|
||||
}
|
||||
last_position_steps_ = 0;
|
||||
|
||||
last_poll_ = 0.0;
|
||||
}
|
||||
|
||||
C804Axis::~C804Axis(void) {
|
||||
// Since the controller memory is managed somewhere else, we don't need to
|
||||
// clean up the pointer pC here.
|
||||
}
|
||||
|
||||
/*
|
||||
The polling function informs us about the state of the axis, in particular if it
|
||||
is currently moving. It is called periodically, with the period defined by
|
||||
the controller constructor arguments idlePollPeriod and movingPollPeriod
|
||||
depending on the current axis state.
|
||||
*/
|
||||
asynStatus C804Axis::poll(bool *moving) {
|
||||
// Local variable declaration
|
||||
static const char *functionName = "C804Axis::poll";
|
||||
|
||||
// The poll function is just a wrapper around poll_no_param_lib_update and
|
||||
// handles mainly the callParamCallbacks() function
|
||||
asynStatus status_poll = C804Axis::poll_no_param_lib_update(moving);
|
||||
|
||||
// According to the function documentation of asynMotorAxis::poll, this
|
||||
// function should be called at the end of a poll implementation.
|
||||
asynStatus status_callback = callParamCallbacks();
|
||||
if (status_callback != asynSuccess) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Updating the parameter library failed for axis %d\n",
|
||||
functionName, axisNo_);
|
||||
return status_callback;
|
||||
} else {
|
||||
return status_poll;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the actual poll
|
||||
asynStatus C804Axis::poll_no_param_lib_update(bool *moving) {
|
||||
// Local variable declaration
|
||||
static const char *functionName = "C804Axis::poll";
|
||||
asynStatus status;
|
||||
int axis_status = 0;
|
||||
|
||||
// The controller returns the position and velocity in encoder steps.
|
||||
// This value needs to be converted in user units (engineering units EGU)
|
||||
// via the record field MRES of the motor record. This field has already
|
||||
// been read by the constructor into the member variable
|
||||
// motorRecResolution_. To go from steps to user units, multiply with
|
||||
// motorRecResolution_ Example: If 10 steps correspond to 1 mm, MRES should
|
||||
// be 0.1.
|
||||
int position_error_steps = 0;
|
||||
int motor_position_steps = 0;
|
||||
int motor_velocity_steps = 0;
|
||||
int programmed_motor_velocity_steps = 0;
|
||||
double position_error = .0;
|
||||
double motor_position = .0;
|
||||
double motor_velocity = .0;
|
||||
double programmed_motor_velocity = .0;
|
||||
|
||||
// The buffer sizes for command and response are defined in the controller
|
||||
// (see the corresponding source code files)
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
|
||||
/*
|
||||
Cancel the poll if the last poll has "just" happened.
|
||||
*/
|
||||
if (time(NULL) < last_poll_ + 0.5 * pC_->movingPollPeriod_) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING,
|
||||
"%s: Aborted poll since the last poll for axis %d happened a "
|
||||
"short time ago\n",
|
||||
functionName, axisNo_);
|
||||
return asynSuccess;
|
||||
} else {
|
||||
last_poll_ = time(NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
The parameter motorRecResolution_ is coupled to the field MRES of the motor
|
||||
record in the following manner:
|
||||
- In sinq_asyn_motor.db, the PV (motor_record_pv_name)MOTOR_REC_RESOLUTION
|
||||
is defined as a copy of the field (motor_record_pv_name).MRES:
|
||||
|
||||
record(ao,"$(P)$(M):Resolution") {
|
||||
field(DESC, "$(M) resolution")
|
||||
field(DOL, "$(P)$(M).MRES CP MS")
|
||||
field(OMSL, "closed_loop")
|
||||
field(DTYP, "asynFloat64")
|
||||
field(OUT, "@asyn($(PORT),$(ADDR))MOTOR_REC_RESOLUTION")
|
||||
field(PREC, "$(PREC)")
|
||||
}
|
||||
|
||||
- The PV name MOTOR_REC_RESOLUTION is coupled in asynMotorController.h to
|
||||
the constant motorRecResolutionString
|
||||
- ... which in turn is assigned to motorRecResolution_ in
|
||||
asynMotorController.cpp This way of making the field visible to the driver
|
||||
is described here: https://epics.anl.gov/tech-talk/2020/msg00378.php This is
|
||||
a one-way coupling, changes to the parameter library via setDoubleParam are
|
||||
NOT transferred to (motor_record_pv_name).MRES or to
|
||||
(motor_record_pv_name):Resolution.
|
||||
|
||||
NOTE: This function must not be called in the constructor (e.g. in order to
|
||||
save the read result to the member variable earlier), since the parameter
|
||||
library is updated at a later stage!
|
||||
*/
|
||||
pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
|
||||
&motorRecResolution_);
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW, "Poll axis %d\n", axisNo_);
|
||||
|
||||
/*
|
||||
We know that the motor resolution must not be zero. During the startup of
|
||||
the IOC, polls can happen before the record is fully initialized. In that
|
||||
case, all values are zero.
|
||||
*/
|
||||
if (motorRecResolution_ == 0) {
|
||||
return asynError;
|
||||
}
|
||||
|
||||
/*
|
||||
Assume that the axis does not have a status problem. If it does have a
|
||||
problem, this value will be overwritten further below. Setting this value
|
||||
in itself does not trigger a callback immediately, any callbacks
|
||||
(such as e.g. updating camonitor) are done in callParamCallbacks() at the
|
||||
end of this function.
|
||||
*/
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
|
||||
// Read out the position error of the axis (delta of target position to
|
||||
// actual position)
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dTE", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dE%10d", &parsed_axis, &position_error_steps);
|
||||
|
||||
// Scale from the encoder resultion to user units
|
||||
position_error = double(position_error_steps) * motorRecResolution_;
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, position error %f\n", functionName,
|
||||
axisNo_, response, position_error);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the position error failed for axis %d\n",
|
||||
functionName, axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
// Stop the evaluation prematurely
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the current position.
|
||||
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTP", this->axisNo_);
|
||||
status =
|
||||
this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dP%10d", &parsed_axis, &motor_position_steps);
|
||||
|
||||
// Scale from the encoder resultion to user units
|
||||
motor_position = double(motor_position_steps) * motorRecResolution_;
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, position %f\n", functionName,
|
||||
axisNo_, response, motor_position);
|
||||
setDoubleParam(pC_->motorPosition_, motor_position);
|
||||
setDoubleParam(pC_->motorEncoderPosition_, motor_position);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the position failed for axis %d\n", functionName,
|
||||
axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the current velocity
|
||||
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTV", this->axisNo_);
|
||||
status =
|
||||
this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dV%10d", &parsed_axis, &motor_velocity_steps);
|
||||
|
||||
// Scale from the encoder resultion to user units
|
||||
motor_velocity = double(motor_velocity_steps) * motorRecResolution_;
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, velocity %f\n", functionName,
|
||||
axisNo_, response, motor_velocity);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the velocity failed for axis %d\n", functionName,
|
||||
axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the programmed velocity
|
||||
snprintf(command, this->pC_->C804_MAXBUF_ - 1, "%dTY", this->axisNo_);
|
||||
status =
|
||||
this->pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dY%10d", &parsed_axis,
|
||||
&programmed_motor_velocity_steps);
|
||||
|
||||
// Scale from the encoder resultion to user units
|
||||
programmed_motor_velocity =
|
||||
double(programmed_motor_velocity_steps) * motorRecResolution_;
|
||||
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, programmed velocity %f\n",
|
||||
functionName, axisNo_, response, programmed_motor_velocity);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the programmed velocity failed for axis %d\n",
|
||||
functionName, axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Read the motor status
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dTS", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(this->axisNo_, command, response, true);
|
||||
if (status == asynSuccess) {
|
||||
int parsed_axis;
|
||||
sscanf(response, "%2dS%10d", &parsed_axis, &axis_status);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
|
||||
"%s: Axis %d, response %s, status %d\n", functionName,
|
||||
axisNo_, response, axis_status);
|
||||
} else {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Reading the motor status %d\n", functionName, axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
|
||||
// Stop prematurely
|
||||
return status;
|
||||
}
|
||||
|
||||
// Check if the axis is enabled by reading out bit 2 (see
|
||||
// https://stackoverflow.com/questions/2249731/how-do-i-get-bit-by-bit-data-from-an-integer-value-in-c)
|
||||
int mask = 1 << 2;
|
||||
int masked_n = axis_status & mask;
|
||||
// Is 1 if the axis is disabled
|
||||
int disabled = masked_n >> 2;
|
||||
if (disabled) {
|
||||
enabled_ = false;
|
||||
} else {
|
||||
enabled_ = true;
|
||||
}
|
||||
|
||||
/*
|
||||
Determine if the motor is moving. This is determined by the following
|
||||
criteria: 1) The motor position changes from poll to poll 2) The motor is
|
||||
enabled
|
||||
*/
|
||||
*moving = enabled_ && motor_position_steps != this->last_position_steps_;
|
||||
|
||||
// Update the cached_position
|
||||
this->last_position_steps_ = motor_position_steps;
|
||||
|
||||
/*
|
||||
Calculate the time the motor should need to reach its target, based on the
|
||||
programmed velocity and compare this to the actual time the motor has spent
|
||||
moving. If it has spent too much time in a moving state without reaching
|
||||
the target, stop the motor and return an error.
|
||||
*/
|
||||
if (*moving) {
|
||||
|
||||
int motorStatusMoving = 0;
|
||||
pC_->getIntegerParam(axisNo_, pC_->motorStatusMoving_,
|
||||
&motorStatusMoving);
|
||||
|
||||
// motor is moving, but didn't move in the last poll
|
||||
if (motorStatusMoving == 0) {
|
||||
time_t current_time = time(NULL);
|
||||
|
||||
// Factor 2 of the calculated moving time
|
||||
estimatedArrivalTime_ =
|
||||
current_time + std::ceil(2 * std::fabs(position_error) /
|
||||
programmed_motor_velocity);
|
||||
} else {
|
||||
// /*
|
||||
// Motor is moving for a longer time than it should: Stop it
|
||||
// */
|
||||
// if (time(NULL) > estimatedArrivalTime_)
|
||||
// {
|
||||
// snprintf(command, pC_->C804_MAXBUF_ - 1, "%dST", axisNo_);
|
||||
// status = pC_->lowLevelWriteRead(axisNo_, command, response);
|
||||
// asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "%s: Stopped
|
||||
// axis %d since it moved for double the time it should to reach
|
||||
// its target\n", functionName, axisNo_);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
One of these parameters (or both) are used to set (PV-name).DMOV.
|
||||
This PV tells EPICS whether the axis / motor is currently moving or not.
|
||||
*/
|
||||
setIntegerParam(pC_->motorStatusMoving_, *moving);
|
||||
setIntegerParam(pC_->motorStatusDone_, !(*moving));
|
||||
callParamCallbacks();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus C804Axis::move(double position, int relative, double minVelocity,
|
||||
double maxVelocity, double acceleration) {
|
||||
asynStatus status;
|
||||
static const char *functionName = "C804Axis::move";
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
double position_c_units = 0.0; // Controller units
|
||||
int position_steps = 0;
|
||||
|
||||
// Convert from user coordinates (EGU) to controller coordinates (steps).
|
||||
// Check for overflow
|
||||
if (motorRecResolution_ == 0.0) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: MRES must not be zero. Movement is aborted",
|
||||
functionName);
|
||||
return asynError;
|
||||
}
|
||||
position_c_units = position / motorRecResolution_;
|
||||
|
||||
// Check for overflow during the division
|
||||
if (position_c_units * motorRecResolution_ != position) {
|
||||
asynPrint(
|
||||
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: could not convert from user units (%f) to controller units "
|
||||
"(user units divided by resolution MRES %f) due to overflow.",
|
||||
functionName, position, motorRecResolution_);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// Steps can only be integer values => cast to integer while checking for
|
||||
// overflow
|
||||
if (std::numeric_limits<int>::max() < position_c_units ||
|
||||
std::numeric_limits<int>::min() > position_c_units) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: target position %f cannot be converted to int "
|
||||
"(overflow). Check target value %f and MRES %f",
|
||||
functionName, position_c_units, position_c_units,
|
||||
motorRecResolution_);
|
||||
return asynError;
|
||||
}
|
||||
position_steps = static_cast<int>(position_c_units);
|
||||
|
||||
// Convert from relative to absolute values
|
||||
if (relative) {
|
||||
position_steps += last_position_steps_;
|
||||
}
|
||||
|
||||
// If the axis is currently disabled, enable it
|
||||
if (!enabled_) {
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dGO", axisNo_);
|
||||
status =
|
||||
pC_->lowLevelWriteRead(this->axisNo_, command, response, false);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Enabling axis %d\n failed", functionName, axisNo_);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// Start movement
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMA%d", axisNo_, position_steps);
|
||||
status = pC_->lowLevelWriteRead(this->axisNo_, command, response, false);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s: Setting the target position %d failed for axis %d\n",
|
||||
functionName, position_steps, axisNo_);
|
||||
setIntegerParam(pC_->motorStatusProblem_, true);
|
||||
return status;
|
||||
}
|
||||
setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
|
||||
// Reset the error flag
|
||||
errorReported_ = 0;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus C804Axis::moveVelocity(double min_velocity, double max_velocity,
|
||||
double acceleration) {
|
||||
static const char *functionName = "C804Axis::moveVelocity";
|
||||
return asynError;
|
||||
}
|
||||
|
||||
asynStatus C804Axis::stop(double acceleration) {
|
||||
asynStatus status = asynSuccess;
|
||||
static const char *functionName = "C804Axis::stop";
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
|
||||
bool moving = false;
|
||||
|
||||
poll(&moving);
|
||||
if (moving) {
|
||||
// ST = Stop
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dST", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Stop axis %d\n",
|
||||
functionName, axisNo_);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus C804Axis::home(double minVelocity, double maxVelocity,
|
||||
double acceleration, int forwards) {
|
||||
asynStatus status = asynSuccess;
|
||||
static const char *functionName = "C804Axis::home";
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dFE0",
|
||||
axisNo_); // Home to the upper limit of the axis (25 mm)
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE, "%s: Homing axis %d\n",
|
||||
functionName, axisNo_);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
If on is 0, disable the motor, otherwise enable it.
|
||||
*/
|
||||
asynStatus C804Axis::enable(int on) {
|
||||
asynStatus status = asynSuccess;
|
||||
static const char *functionName = "C804Axis::enable";
|
||||
char command[pC_->C804_MAXBUF_], response[pC_->C804_MAXBUF_];
|
||||
|
||||
if (on == 0) {
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMF", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE,
|
||||
"%s: Disable axis %d\n", functionName, axisNo_);
|
||||
} else {
|
||||
snprintf(command, pC_->C804_MAXBUF_ - 1, "%dMN", axisNo_);
|
||||
status = pC_->lowLevelWriteRead(axisNo_, command, response, false);
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACEIO_DEVICE,
|
||||
"%s: Enable axis %d\n", functionName, axisNo_);
|
||||
}
|
||||
return status;
|
||||
}
|
40
sinqEPICSApp/src/C804Axis.h
Normal file
40
sinqEPICSApp/src/C804Axis.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef C804Axis_H
|
||||
#define C804Axis_H
|
||||
|
||||
#include "SINQController.h"
|
||||
#include "SINQAxis.h"
|
||||
|
||||
// Forward declaration of the controller class to resolve the cyclic dependency
|
||||
// between C804Controller.h and C804Axis.h. See https://en.cppreference.com/w/cpp/language/class.
|
||||
class C804Controller;
|
||||
|
||||
class C804Axis : public SINQAxis
|
||||
{
|
||||
public:
|
||||
/* These are the methods we override from the base class */
|
||||
C804Axis(C804Controller *pController, int axisNo);
|
||||
virtual ~C804Axis();
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus home(double minVelocity, double maxVelocity, double acceleration, int forwards);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus poll_no_param_lib_update(bool *moving);
|
||||
asynStatus enable(int on);
|
||||
|
||||
protected:
|
||||
C804Controller *pC_;
|
||||
|
||||
void checkBounds(C804Controller *pController, int axisNo);
|
||||
int last_position_steps_;
|
||||
double motorRecResolution_;
|
||||
time_t estimatedArrivalTime_;
|
||||
time_t last_poll_;
|
||||
int errorReported_;
|
||||
bool enabled_;
|
||||
|
||||
private:
|
||||
friend class C804Controller;
|
||||
};
|
||||
|
||||
#endif
|
458
sinqEPICSApp/src/C804Controller.cpp
Normal file
458
sinqEPICSApp/src/C804Controller.cpp
Normal file
@ -0,0 +1,458 @@
|
||||
/**
|
||||
Overview EPICS documentation
|
||||
- https://docs.epics-controls.org/en/latest/index.html
|
||||
- https://epics.anl.gov/modules/soft/asyn/R4-29/asynDriver.html
|
||||
- https://www.physicstom.com/epics/
|
||||
- https://epics.anl.gov/modules/soft/asyn/R4-20/asynDriver.pdf
|
||||
*/
|
||||
|
||||
#include "C804Controller.h"
|
||||
#include "asynOctetSyncIO.h"
|
||||
#include <errlog.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <iocsh.h>
|
||||
#include <epicsExport.h>
|
||||
#include <registryFunction.h>
|
||||
|
||||
const double C804Controller::C804_TIMEOUT_ = 5.0;
|
||||
|
||||
static const char *driverName = "C804Controller";
|
||||
/*
|
||||
In SINQ, this constructor is usually called by its C interface function via the IOC shell.
|
||||
A typical call looks like that (taken from SANS instrument st.cmd):
|
||||
|
||||
pmacAsynIPConfigure("pmcu1","sans1-mcu1:1025")
|
||||
pmacV3CreateController("mcu1","pmcu1",0,9,50,10000);
|
||||
pmacV3CreateAxis("mcu1",1,0);
|
||||
pmacV3CreateAxis("mcu1",2,0);
|
||||
pmacV3CreateAxis("mcu1",3,0);
|
||||
pmacV3CreateAxis("mcu1",4,0);
|
||||
pmacV3CreateAxis("mcu1",5,0);
|
||||
pmacV3CreateAxis("mcu1",6,0);
|
||||
pmacV3CreateAxis("mcu1",7,0);
|
||||
pmacV3CreateAxis("mcu1",8,0);
|
||||
|
||||
The first call creates a port object in EPICS (port 1025 in this specific case) with the name "pmcu1".
|
||||
|
||||
The second call creates the controller with the following arguments:
|
||||
- portName = "mcu1": The controller is registered by this name in EPICS. The axes
|
||||
constructors below use the name to get the controller pointer from EPICS.
|
||||
- lowLevelPortName = "pmcu1": The EPICS controller object connects to the physical
|
||||
device via the port object "pmcu1".
|
||||
- lowLevelPortAddress = 0: Connects to port 0 of asynOctetSyncIO. asynOctetSyncIO
|
||||
is an interface for ASCII-string-based message communication between driver and device.
|
||||
This value seems to be always zero (at least for all pmacAsynIPConfigure in the SINQ IOCs)
|
||||
- numAxes = 9: Constructs the array "pAxes_" with space for 9 axis pointers (see below).
|
||||
In the axes constructors below, we use the array indices 1 to 8, hence leaving the
|
||||
first element of the array unitialized (it has index 0). This is mainly a convenience
|
||||
to avoid talking about an "axis 0" when meaning the first axis.
|
||||
- movingPollPeriod: The method C804Controller::poll is called in a loop every
|
||||
movingPollPeriod seconds when the axis is moving
|
||||
- idlePollPeriod: The method C804Controller::poll is called in a loop every
|
||||
idlePollPeriod seconds when the axis is not moving
|
||||
*/
|
||||
C804Controller::C804Controller(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress,
|
||||
int numAxes, double movingPollPeriod, double idlePollPeriod, const int &extraParams)
|
||||
: SINQController(portName, lowLevelPortName, numAxes, extraParams)
|
||||
{
|
||||
|
||||
// Definition of local variables.
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
/*
|
||||
functionName is overwritten by the name of the current function in each method
|
||||
of C804Controller. This is used for feedback messages to the user.
|
||||
*/
|
||||
static const char *functionName = "C804Controller::C804Controller";
|
||||
|
||||
/*
|
||||
Update: We don't need this array, since all axes accesses are done
|
||||
with the getAxis-function, which accesses the base class array
|
||||
|
||||
============================================================================
|
||||
|
||||
The array pAxes_ is a member of the superclass asynMotorController and is
|
||||
allocated when calling the constructor of asynMotorController, which
|
||||
is done by the constructor of SINQController:
|
||||
|
||||
pAxes_ = (asynMotorAxis**) calloc(numAxes, sizeof(asynMotorAxis*));
|
||||
|
||||
Therefore, on this line we create a pointer to that array while interpreting
|
||||
every pointer in it as one to a C804Axis. This is valid because we populate
|
||||
the array in the constructor of C804Axis and can therefore be sure that
|
||||
all asynMotorAxis pointers in asynMotorController::pAxes_ are in fact pointers
|
||||
to C804Axis axes.
|
||||
|
||||
Interestingly, this array usually is leaked after destruction of asynMotorController
|
||||
(n this class, we clean it up in the constructor).
|
||||
The reason for that is that the EPICS devices are usually created only once at
|
||||
the start of the program and the memory is cleaned up by the OS.
|
||||
*/
|
||||
// pAxes_ = (C804Axis **)(asynMotorController::pAxes_);
|
||||
|
||||
// Initialize non static data members
|
||||
lowLevelPortUser_ = NULL;
|
||||
movingPollPeriod_ = movingPollPeriod;
|
||||
idlePollPeriod_ = idlePollPeriod;
|
||||
|
||||
/*
|
||||
We try to connect to the port via the port name provided by the constructor.
|
||||
If this fails, we return an error message.
|
||||
*/
|
||||
status = pasynOctetSyncIO->connect(lowLevelPortName, lowLevelPortAddress, &lowLevelPortUser_, NULL);
|
||||
if (status != asynSuccess)
|
||||
{
|
||||
/*
|
||||
ASYN_TRACE_ERROR is a mask for the trace of asynUser (member variable this->lowLevelPortUser_)
|
||||
The different mask options are listed on this page: https://epics.anl.gov/modules/soft/asyn/R4-29/asynDriver.html (section AsynTrace):
|
||||
|
||||
0x1 ASYN_TRACE_ERROR Run time errors are reported, e.g. timeouts.
|
||||
0x2 ASYN_TRACEIO_DEVICE Device support reports I/O activity.
|
||||
0x4 ASYN_TRACEIO_FILTER Any layer between device support and the low level driver reports any filtering it does on I/O.
|
||||
0x8 ASYN_TRACEIO_DRIVER Low level driver reports I/O activity.
|
||||
0x10 ASYN_TRACE_FLOW Report logic flow. Device support should report all queue requests, callbacks entered, and all calls to drivers. Layers between device support and low level drivers should report all calls they make to lower level drivers. Low level drivers report calls they make to other support.
|
||||
0x20 ASYN_TRACE_WARNING Report warnings, i.e. conditions that are between ASYN_TRACE_ERROR and ASYN_TRACE_FLOW.
|
||||
|
||||
To see the output of these functions e.g. on the shell, a trace mask needs
|
||||
to be set:
|
||||
|
||||
asynSetTraceIOMask("L0", -1, 0x2)
|
||||
asynSetTraceMask("L0", -1, 0x9) <- this enables 0x8 + 0x1 => ASYN_TRACE_ERROR and ASYN_TRACEIO_DRIVER
|
||||
|
||||
https://github-wiki-see.page/m/ISISComputingGroup/ibex_developers_manual/wiki/ASYN-Trace-Masks-(Debugging-IOC,-ASYN)
|
||||
|
||||
Here, we are unable to connect to the controller, which is a runtime error -> ASYN_TRACE_ERROR.
|
||||
However, since lowLevelPortUser_ might still be NULL (because
|
||||
pasynOctetSyncIO->connect failed for some reason), we use the alternative
|
||||
EPICS function errlogPrintf. This function does not provide a timestamp
|
||||
and masking facilities.
|
||||
*/
|
||||
// asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
// "%s: cannot connect to C804 controller\n",
|
||||
// functionName); // Asyn-framework function, needs to be configured by setTraceMasks
|
||||
errlogPrintf("Fatal error in %s: cannot connect to C804 controller\n", functionName);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/*
|
||||
Here we define the terminators for messages sent to / received from the
|
||||
physical device (Eos = End of string).
|
||||
In the C804 manual, the terminator for an outgoing message is specified as
|
||||
(rtn) == Carriage Return. From https://en.wikipedia.org/wiki/Escape_sequences_in_C:
|
||||
(rtn) => \r
|
||||
|
||||
An incoming report is terminated by a CRLF ETX (again referring to https://en.wikipedia.org/wiki/Escape_sequences_in_C)
|
||||
CR => \r
|
||||
LF (line feed) = \n
|
||||
ETX (end-of-text, see https://www.asciitable.com/) => \x03
|
||||
*/
|
||||
const char *message_to_device = "\r";
|
||||
const char *message_from_device = "\x03";
|
||||
pasynOctetSyncIO->setOutputEos(lowLevelPortUser_, message_to_device, strlen(message_to_device)); // Output: from EPICS to device
|
||||
pasynOctetSyncIO->setInputEos(lowLevelPortUser_, message_from_device, strlen(message_from_device)); // Input: from device to EPICS
|
||||
|
||||
/*
|
||||
See documentation of function in asynMotorController.cpp:
|
||||
"Starts the motor poller thread.
|
||||
* Derived classes will typically call this at near the end of their constructor.
|
||||
[...]
|
||||
"
|
||||
The function arguments are:
|
||||
* movingPollPeriod The time between polls when any axis is moving (in seconds).
|
||||
* idlePollPeriod The time between polls when no axis is moving (in seconds).
|
||||
* forcedFastPolls The number of times to force the movingPollPeriod after waking up the poller.
|
||||
*/
|
||||
startPoller(movingPollPeriod, idlePollPeriod, 1);
|
||||
|
||||
/*
|
||||
After changing values in the parameter library (e.g. by calls to setIntegerParam),
|
||||
the PV's need to be updated. This is done explictly by callParamCallbacks()
|
||||
callParamCallbacks due to the separation asyn - EPICS (param lib vs. driver support)
|
||||
*/
|
||||
callParamCallbacks();
|
||||
}
|
||||
|
||||
C804Controller::~C804Controller(void)
|
||||
{
|
||||
/*
|
||||
Cleanup of the memory allocated in this->pAxes_. As discussed in the constructor,
|
||||
this is not strictly necessary due to the way EPICS works, but it is good
|
||||
practice anyway to properly clean up resources.
|
||||
*/
|
||||
free(this->pAxes_);
|
||||
}
|
||||
|
||||
/*
|
||||
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 C804Axis, a nullptr is returned and an error is emitted.
|
||||
*/
|
||||
C804Axis *C804Controller::getAxis(asynUser *pasynUser)
|
||||
{
|
||||
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
|
||||
return C804Controller::castToC804Axis(asynAxis);
|
||||
}
|
||||
|
||||
/*
|
||||
Access one of the axes of the controller via the axis index.
|
||||
If the axis does not exist or is not a C804Axis, the function must return Null
|
||||
*/
|
||||
C804Axis *C804Controller::getAxis(int axisNo)
|
||||
{
|
||||
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
|
||||
return C804Controller::castToC804Axis(asynAxis);
|
||||
}
|
||||
|
||||
C804Axis *C804Controller::castToC804Axis(asynMotorAxis *asynAxis)
|
||||
{
|
||||
static const char *functionName = "C804Controller::getAxis";
|
||||
|
||||
// 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 C804Axis
|
||||
C804Axis *axis = dynamic_cast<C804Axis *>(asynAxis);
|
||||
if (axis == nullptr)
|
||||
{
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR, "%s: Axis %d is not a C804 Axis", functionName, axis->axisNo_);
|
||||
}
|
||||
return axis;
|
||||
}
|
||||
|
||||
/*
|
||||
Sends the given command to the axis specified by axisNo and returns the response
|
||||
of the axis.
|
||||
*/
|
||||
asynStatus C804Controller::lowLevelWriteRead(int axisNo, const char *command, char *response, bool expect_response)
|
||||
{
|
||||
// Definition of local variables.
|
||||
static const char *functionName = "C804Controller::lowLevelWriteRead";
|
||||
asynStatus status = asynSuccess;
|
||||
C804Axis *axis = getAxis(axisNo);
|
||||
|
||||
if (axis == nullptr)
|
||||
{
|
||||
// We already did the error logging directly in getAxis
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// TBD: Is this interpretation correct?
|
||||
int eomReason = 0; // Flag indicating why the message has ended
|
||||
size_t nbytesOut = 0; // Number of bytes of the outgoing message (which is command + the end-of-string terminator defined in the constructor)
|
||||
size_t nbytesIn = 0; // Number of bytes of the incoming message (which is response + the end-of-string terminator defined in the constructor)
|
||||
|
||||
// If the class instance could not be connected to the device, set an error flag.
|
||||
if (lowLevelPortUser_ == nullptr)
|
||||
{
|
||||
asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
"%s: not connected to C804 controller\n",
|
||||
functionName);
|
||||
|
||||
// Adjust the parameter library
|
||||
setIntegerParam(this->motorStatusCommsError_, 1);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// Mask ASYN_TRACEIO_DRIVER is defined as "Device support reports I/O activity"
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, "%s: command: %s\n", functionName, command);
|
||||
|
||||
// Writes the command to the port and blocks until a response has been received or until the timeout has been reached.
|
||||
// For some inputs (such as TP = Tell position), we expect a response which is terminated by a character array "x03"
|
||||
// Other messages such as MA100 (move) don't return a response
|
||||
if (expect_response)
|
||||
{
|
||||
status = pasynOctetSyncIO->writeRead(lowLevelPortUser_,
|
||||
command, strlen(command),
|
||||
response, this->C804_MAXBUF_,
|
||||
C804_TIMEOUT_,
|
||||
&nbytesOut, &nbytesIn, &eomReason);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = pasynOctetSyncIO->write(lowLevelPortUser_,
|
||||
command, strlen(command),
|
||||
C804_TIMEOUT_,
|
||||
&nbytesOut);
|
||||
}
|
||||
|
||||
// Writing and/or reading succeded
|
||||
if (status == asynSuccess)
|
||||
{
|
||||
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, "%s: device response: %s\n", functionName, response);
|
||||
|
||||
// Reset any error which might have been set
|
||||
setIntegerParam(this->motorStatusCommsError_, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
asynPrint(this->lowLevelPortUser_, ASYN_TRACE_ERROR,
|
||||
"%s: asynOctetSyncIO->writeRead failed for command %s on axis %d\n",
|
||||
functionName, command, axisNo);
|
||||
|
||||
setIntegerParam(this->motorStatusCommsError_, 1);
|
||||
}
|
||||
|
||||
// Block the thread to avoid sending too many messages in a short timeframe
|
||||
usleep(interMessageSleep);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*************************************************************************************/
|
||||
/** The following functions are C-wrappers, and can be called directly from iocsh */
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
/*
|
||||
C wrapper for the C804Controller constructor.
|
||||
*/
|
||||
asynStatus C804CreateController(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress,
|
||||
int numAxes, double movingPollPeriod, double idlePollPeriod)
|
||||
{
|
||||
/*
|
||||
We create a new instance of C804CreateController, using the "new" keyword to allocate it
|
||||
on the heap while avoiding RAII.
|
||||
TBD: Where is the pointer to the controller stored?
|
||||
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
|
||||
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
|
||||
|
||||
Setting the pointer to nullptr / NULL immediately after construction is simply
|
||||
done to avoid compiler warnings, see page 7 of this document:
|
||||
https://subversion.xray.aps.anl.gov/synApps/measComp/trunk/documentation/measCompTutorial.pdf
|
||||
*/
|
||||
C804Controller *pController = new C804Controller(portName, lowLevelPortName, lowLevelPortAddress, numAxes, movingPollPeriod, idlePollPeriod);
|
||||
pController = nullptr;
|
||||
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
C wrapper for the C804Axis constructor.
|
||||
See C804Axis::C804Axis.
|
||||
*/
|
||||
asynStatus C804CreateAxis(const char *C804Name, int axis)
|
||||
{
|
||||
C804Axis *pAxis;
|
||||
|
||||
static const char *functionName = "C804CreateAxis";
|
||||
|
||||
/*
|
||||
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 C804Controller
|
||||
https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
|
||||
*/
|
||||
void *ptr = findAsynPortDriver(C804Name);
|
||||
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
|
||||
*/
|
||||
printf("%s:%s: Error port %s not found\n", driverName, functionName, C804Name);
|
||||
return asynError;
|
||||
}
|
||||
// Unsafe cast of the pointer to an asynPortDriver
|
||||
asynPortDriver *apd = (asynPortDriver *)(ptr);
|
||||
|
||||
// Safe downcast
|
||||
C804Controller *pC = dynamic_cast<C804Controller *>(apd);
|
||||
if (pC == nullptr)
|
||||
{
|
||||
printf("%s: controller on port %s is not a C804Controller\n", functionName, C804Name);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
// Prevent manipulation of the controller from other threads while we create the new axis.
|
||||
pC->lock();
|
||||
|
||||
/*
|
||||
We create a new instance of C804Axis, using the "new" keyword to allocate it
|
||||
on the heap while avoiding RAII. In the constructor, a pointer to the new object is stored in
|
||||
the controller object "pC". Therefore, the axis instance can still be
|
||||
reached later by quering "pC".
|
||||
|
||||
Setting the pointer to nullptr / NULL immediately after construction is simply
|
||||
done to avoid compiler warnings, see page 7 of this document:
|
||||
https://subversion.xray.aps.anl.gov/synApps/measComp/trunk/documentation/measCompTutorial.pdf
|
||||
*/
|
||||
pAxis = new C804Axis(pC, axis);
|
||||
pAxis = nullptr;
|
||||
|
||||
// Allow manipulation of the controller again
|
||||
pC->unlock();
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/*
|
||||
This is boilerplate code which is used to make the FFI functions
|
||||
C804CreateController and C804CreateAxis "known" to the IOC shell (iocsh).
|
||||
TBD: If the code is compiled for running on vxWorks, this registration is
|
||||
apparently not necessary?
|
||||
*/
|
||||
|
||||
#ifdef vxWorks
|
||||
#else
|
||||
|
||||
/*
|
||||
Define name and type of the arguments for the C804CreateController function
|
||||
in the iocsh. This is done by creating structs with the argument names and types
|
||||
and then providing "factory" functions (configC804CreateControllerCallFunc).
|
||||
These factory functions are used to register the constructors during compilation.
|
||||
*/
|
||||
static const iocshArg C804CreateControllerArg0 = {"Controller port name", iocshArgString};
|
||||
static const iocshArg C804CreateControllerArg1 = {"Low level port name", iocshArgString};
|
||||
static const iocshArg C804CreateControllerArg2 = {"Low level port address", iocshArgInt};
|
||||
static const iocshArg C804CreateControllerArg3 = {"Number of axes", iocshArgInt};
|
||||
static const iocshArg C804CreateControllerArg4 = {"Moving poll rate (s)", iocshArgDouble};
|
||||
static const iocshArg C804CreateControllerArg5 = {"Idle poll rate (s)", iocshArgDouble};
|
||||
static const iocshArg *const C804CreateControllerArgs[] = {&C804CreateControllerArg0,
|
||||
&C804CreateControllerArg1,
|
||||
&C804CreateControllerArg2,
|
||||
&C804CreateControllerArg3,
|
||||
&C804CreateControllerArg4,
|
||||
&C804CreateControllerArg5};
|
||||
static const iocshFuncDef configC804CreateController = {"C804CreateController", 6, C804CreateControllerArgs};
|
||||
static void configC804CreateControllerCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
C804CreateController(args[0].sval, args[1].sval, args[2].ival, args[3].ival, args[4].dval, args[5].dval);
|
||||
}
|
||||
|
||||
/*
|
||||
Same procedure as for the C804CreateController function, but for the axis itself.
|
||||
*/
|
||||
static const iocshArg C804CreateAxisArg0 = {"Controller port name", iocshArgString};
|
||||
static const iocshArg C804CreateAxisArg1 = {"Axis number", iocshArgInt};
|
||||
static const iocshArg *const C804CreateAxisArgs[] = {&C804CreateAxisArg0,
|
||||
&C804CreateAxisArg1};
|
||||
static const iocshFuncDef configC804CreateAxis = {"C804CreateAxis", 2, C804CreateAxisArgs};
|
||||
static void configC804CreateAxisCallFunc(const iocshArgBuf *args)
|
||||
{
|
||||
C804CreateAxis(args[0].sval, args[1].ival);
|
||||
}
|
||||
|
||||
// This function is made known to EPICS in sinq.dbd and is called by EPICS
|
||||
// in order to register both functions in the IOC shell
|
||||
// TBD: Does this happen during compilation?
|
||||
static void C804ControllerRegister(void)
|
||||
{
|
||||
iocshRegister(&configC804CreateController, configC804CreateControllerCallFunc);
|
||||
iocshRegister(&configC804CreateAxis, configC804CreateAxisCallFunc);
|
||||
}
|
||||
epicsExportRegistrar(C804ControllerRegister);
|
||||
|
||||
#endif
|
||||
|
||||
} // extern "C"
|
50
sinqEPICSApp/src/C804Controller.h
Normal file
50
sinqEPICSApp/src/C804Controller.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef C804Controller_H
|
||||
#define C804Controller_H
|
||||
|
||||
#include "SINQController.h"
|
||||
#include "C804Axis.h"
|
||||
#include "asynMotorAxis.h"
|
||||
|
||||
class C804Controller : public SINQController
|
||||
{
|
||||
public:
|
||||
C804Controller(const char *portName, const char *lowLevelPortName, int lowLevelPortAddress, int numAxes, double movingPollPeriod,
|
||||
double idlePollPeriod, const int &extraParams = 2);
|
||||
|
||||
virtual ~C804Controller();
|
||||
|
||||
/* These are the methods that we override */
|
||||
C804Axis *getAxis(asynUser *pasynUser);
|
||||
C804Axis *getAxis(int axisNo);
|
||||
C804Axis *castToC804Axis(asynMotorAxis *asynAxis);
|
||||
|
||||
protected:
|
||||
asynUser *lowLevelPortUser_;
|
||||
|
||||
// User-defined polling periods in ms
|
||||
time_t movingPollPeriod_;
|
||||
time_t idlePollPeriod_;
|
||||
|
||||
void log(const char *message);
|
||||
asynStatus lowLevelWriteRead(int axisNo, const char *command, char *response, bool expect_response);
|
||||
|
||||
private:
|
||||
// Set the maximum buffer size. This is an empirical value which must be large
|
||||
// enough to avoid overflows for all commands to the device / responses from it.
|
||||
static const uint32_t C804_MAXBUF_ = 200;
|
||||
|
||||
/*
|
||||
When trying to communicate with the device, the underlying asynOctetSyncIO
|
||||
interface waits for a response until this time (in seconds) has passed,
|
||||
then it declares a timeout. This variable has to be specified in the .cpp-file.
|
||||
Tying to specify in the .h-file results in the following compiler error:
|
||||
In file included from ../sinqEPICSApp/src/C804Axis.cpp:2:
|
||||
../sinqEPICSApp/src/C804Controller.h:38:23: error: ‘constexpr’ needed for in-class initialization
|
||||
of static data member ‘const double C804Controller::C804_TIMEOUT_’ of non-integral type
|
||||
*/
|
||||
static const double C804_TIMEOUT_;
|
||||
|
||||
friend class C804Axis;
|
||||
};
|
||||
|
||||
#endif
|
@ -143,6 +143,11 @@ asynStatus EL734Controller::transactController(int axisNo, char command[COMLEN],
|
||||
|
||||
pasynOctetSyncIO->flush(pasynUserController_);
|
||||
|
||||
if (axis != NULL)
|
||||
{
|
||||
axis->updateMsgTxtFromDriver("");
|
||||
}
|
||||
|
||||
status = pasynOctetSyncIO->writeRead(pasynUserController_, command, strlen(command),
|
||||
reply, COMLEN, 2., &out, &in, &reason);
|
||||
if (status != asynSuccess)
|
||||
|
@ -250,6 +250,8 @@ PhytronAxis::PhytronAxis(PhytronController *pC, int axisNo, int enc)
|
||||
haveBrake = 0;
|
||||
brakeIO = -1;
|
||||
next_poll = -1;
|
||||
homing = 0;
|
||||
homing_direction = 0;
|
||||
}
|
||||
|
||||
int PhytronAxis::setBrake(int brakeNO)
|
||||
|
@ -114,7 +114,7 @@ typedef struct {
|
||||
unsigned int dbInit;
|
||||
}EL737priv;
|
||||
|
||||
static void dummyAsynCallback([[maybe_unused]] asynUser *pasynUser)
|
||||
static void dummyAsynCallback(asynUser *pasynUser)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1186,6 +1186,8 @@ AmorDetectorAxis::AmorDetectorAxis(pmacController *pC, int axisNo, int function)
|
||||
pC_->debugFlow(functionName);
|
||||
|
||||
_function = function;
|
||||
det_starting = false;
|
||||
det_startTime = time(NULL);
|
||||
|
||||
callParamCallbacks();
|
||||
|
||||
|
@ -1,169 +1,180 @@
|
||||
/********************************************
|
||||
* pmacAxis.cpp
|
||||
*
|
||||
* PMAC Asyn motor based on the
|
||||
*
|
||||
* PMAC Asyn motor based on the
|
||||
* asynMotorAxis class.
|
||||
*
|
||||
*
|
||||
* Matthew Pearson
|
||||
* 23 May 2012
|
||||
*
|
||||
*
|
||||
* Modified to use the MsgTxt field for SINQ
|
||||
*
|
||||
* Mark Koennecke, January 2019
|
||||
*
|
||||
* EXtended with special motor axis for the Selene
|
||||
* EXtended with special motor axis for the Selene
|
||||
* guide, Mark Koennecke, February 2020
|
||||
********************************************/
|
||||
|
||||
#ifndef pmacAxis_H
|
||||
#define pmacAxis_H
|
||||
|
||||
#include "SINQController.h"
|
||||
#include "SINQAxis.h"
|
||||
#include "SINQController.h"
|
||||
|
||||
class pmacController;
|
||||
class SeleneController;
|
||||
|
||||
class pmacAxis : public SINQAxis
|
||||
{
|
||||
class pmacAxis : public SINQAxis {
|
||||
public:
|
||||
/* These are the methods we override from the base class */
|
||||
pmacAxis(pmacController *pController, int axisNo, bool enable=true);
|
||||
virtual ~pmacAxis();
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus home(double min_velocity, double max_velocity, double acceleration, int forwards);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus enable(int on);
|
||||
/* These are the methods we override from the base class */
|
||||
pmacAxis(pmacController *pController, int axisNo, bool enable = true);
|
||||
virtual ~pmacAxis();
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity,
|
||||
double acceleration);
|
||||
asynStatus home(double min_velocity, double max_velocity,
|
||||
double acceleration, int forwards);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus enable(int on);
|
||||
|
||||
protected:
|
||||
pmacController *pC_;
|
||||
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
asynStatus getAxisInitialStatus(void);
|
||||
protected:
|
||||
pmacController *pC_;
|
||||
|
||||
double setpointPosition_;
|
||||
double encoderPosition_;
|
||||
double currentVelocity_;
|
||||
double velocity_;
|
||||
double accel_;
|
||||
double highLimit_;
|
||||
double lowLimit_;
|
||||
double scale_;
|
||||
double previous_position_;
|
||||
int previous_direction_;
|
||||
int encoder_axis_;
|
||||
int axisErrorCount;
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
asynStatus getAxisInitialStatus(void);
|
||||
|
||||
time_t startTime;
|
||||
time_t status6Time;
|
||||
int starting;
|
||||
int homing;
|
||||
double statusPos;
|
||||
double setpointPosition_;
|
||||
double encoderPosition_;
|
||||
double currentVelocity_;
|
||||
double velocity_;
|
||||
double accel_;
|
||||
double highLimit_;
|
||||
double lowLimit_;
|
||||
double scale_;
|
||||
double previous_position_;
|
||||
int previous_direction_;
|
||||
int encoder_axis_;
|
||||
int axisErrorCount;
|
||||
|
||||
time_t next_poll;
|
||||
time_t startTime;
|
||||
time_t status6Time;
|
||||
int starting;
|
||||
int homing;
|
||||
double statusPos;
|
||||
|
||||
bool autoEnable;
|
||||
|
||||
friend class pmacController;
|
||||
friend class pmacV3Controller;
|
||||
time_t next_poll;
|
||||
|
||||
bool autoEnable;
|
||||
|
||||
friend class pmacController;
|
||||
friend class pmacV3Controller;
|
||||
};
|
||||
/*--------------------------------------------------------------------------------------------*/
|
||||
class SeleneAxis : public pmacAxis
|
||||
{
|
||||
class SeleneAxis : public pmacAxis {
|
||||
public:
|
||||
SeleneAxis(SeleneController *pController, int axisNo, double limitTarget);
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus home(double min_velocity, double max_velocity, double acceleration, int forwards);
|
||||
SeleneAxis(SeleneController *pController, int axisNo, double limitTarget);
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus home(double min_velocity, double max_velocity,
|
||||
double acceleration, int forwards);
|
||||
|
||||
protected:
|
||||
friend class SeleneController;
|
||||
friend class pmacController;
|
||||
|
||||
private:
|
||||
double limitTarget;
|
||||
asynStatus getSeleneAxisInitialStatus(void);
|
||||
protected:
|
||||
friend class SeleneController;
|
||||
friend class pmacController;
|
||||
|
||||
private:
|
||||
double limitTarget;
|
||||
asynStatus getSeleneAxisInitialStatus(void);
|
||||
};
|
||||
|
||||
/*
|
||||
Yet another special set of motors for the Selene Guide at AMOR. Each segment can be lifted or tilted. This is
|
||||
two motors. One acts as a slave and only writes a new target, the other also sets a new target and sends the
|
||||
actual movement command. Both motors are coordianted in the motor controller in order to avoid tension on
|
||||
the guide elements. This gaves rise to the function code LIFTSLAVE and LIFTMASTER.
|
||||
Yet another special set of motors for the Selene Guide at AMOR. Each segment
|
||||
can be lifted or tilted. This is two motors. One acts as a slave and only
|
||||
writes a new target, the other also sets a new target and sends the actual
|
||||
movement command. Both motors are coordianted in the motor controller in order
|
||||
to avoid tension on the guide elements. This gaves rise to the function code
|
||||
LIFTSLAVE and LIFTMASTER.
|
||||
|
||||
In another mode the whole guide can be lifted or tilted. Then motor 1 and 6 get new values and one of them
|
||||
sends the drive command. This causes all 6 motors to drive synchronously to their new targets. This is
|
||||
implemented through the LIFTSEGMENT function code.
|
||||
In another mode the whole guide can be lifted or tilted. Then motor 1 and 6
|
||||
get new values and one of them sends the drive command. This causes all 6
|
||||
motors to drive synchronously to their new targets. This is implemented
|
||||
through the LIFTSEGMENT function code.
|
||||
|
||||
Mark Koennecke, February 2020
|
||||
Mark Koennecke, February 2020
|
||||
|
||||
The axis should not be enabled automatically
|
||||
|
||||
Michele Brambilla, February 2020
|
||||
Michele Brambilla, February 2020
|
||||
|
||||
*/
|
||||
class LiftAxis : public pmacAxis
|
||||
{
|
||||
public:
|
||||
LiftAxis(pmacController *pController, int axisNo) : pmacAxis((pmacController *)pController,axisNo) {};
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus stop(double acceleration);
|
||||
private:
|
||||
int waitStart;
|
||||
class LiftAxis : public pmacAxis {
|
||||
public:
|
||||
/*
|
||||
The default constructor of LiftAxis just forwards to the pmacAxis
|
||||
constructor, which has an optional argument "autoenable" with the default
|
||||
value "true". However, we want that argument to be false, hence we provide
|
||||
an explicit constructor.
|
||||
*/
|
||||
LiftAxis(pmacController *pController, int axisNo)
|
||||
: pmacAxis((pmacController *)pController, axisNo, false) {};
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus stop(double acceleration);
|
||||
|
||||
private:
|
||||
int waitStart;
|
||||
};
|
||||
|
||||
/********************************************
|
||||
/********************************************
|
||||
* Protocol version 3 requires just some minor change
|
||||
*
|
||||
* Michele Brambilla, February 2022
|
||||
********************************************/
|
||||
|
||||
class pmacV3Axis : public pmacAxis {
|
||||
public:
|
||||
public:
|
||||
pmacV3Axis(pmacController *pController, int axisNo);
|
||||
|
||||
pmacV3Axis(pmacController *pController, int axisNo);
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
protected:
|
||||
int IsEnable;
|
||||
double Speed;
|
||||
protected:
|
||||
int IsEnable;
|
||||
double Speed;
|
||||
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
|
||||
friend class pmacController;
|
||||
friend class pmacV3Controller;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------------------------*/
|
||||
class pmacHRPTAxis : public pmacV3Axis
|
||||
{
|
||||
public:
|
||||
pmacHRPTAxis(pmacController *pController, int axisNo) : pmacV3Axis(pController,axisNo) {};
|
||||
/**
|
||||
* Override getAxisStatus in order to read the special parameter indicating a
|
||||
* slit blade crash at HRPT
|
||||
*/
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
|
||||
protected:
|
||||
friend class pmacController;
|
||||
|
||||
friend class pmacController;
|
||||
friend class pmacV3Controller;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------------------------*/
|
||||
class pmacHRPTAxis : public pmacV3Axis {
|
||||
public:
|
||||
pmacHRPTAxis(pmacController *pController, int axisNo)
|
||||
: pmacV3Axis(pController, axisNo) {};
|
||||
/**
|
||||
* Override getAxisStatus in order to read the special parameter indicating
|
||||
* a slit blade crash at HRPT
|
||||
*/
|
||||
asynStatus getAxisStatus(bool *moving);
|
||||
|
||||
protected:
|
||||
friend class pmacController;
|
||||
};
|
||||
|
||||
/*
|
||||
* Special motors for the AMOR detector movement. The whole
|
||||
* command set is different but on a pmac controller. This implements
|
||||
* a coordinated movement of cox, coz and ftz in order not to break
|
||||
* the flight tube which may have been mounted. This is mapped to three
|
||||
* motors selected via the function code: com, the detector omega, coz,
|
||||
* motors selected via the function code: com, the detector omega, coz,
|
||||
* the detector z offset and park, for parking the flightpath.
|
||||
*/
|
||||
|
||||
@ -171,39 +182,41 @@ class pmacHRPTAxis : public pmacV3Axis
|
||||
#define ADCOZ 2
|
||||
#define ADPARK 3
|
||||
|
||||
class AmorDetectorAxis: public pmacAxis {
|
||||
public:
|
||||
AmorDetectorAxis(pmacController *pController, int axisNo, int function);
|
||||
class AmorDetectorAxis : public pmacAxis {
|
||||
public:
|
||||
AmorDetectorAxis(pmacController *pController, int axisNo, int function);
|
||||
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus home(double min_velocity, double max_velocity, double acceleration, int forwards);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus setPosition(double position);
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
asynStatus moveVelocity(double min_velocity, double max_velocity,
|
||||
double acceleration);
|
||||
asynStatus home(double min_velocity, double max_velocity,
|
||||
double acceleration, int forwards);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus setPosition(double position);
|
||||
|
||||
protected:
|
||||
int _function;
|
||||
int det_starting;
|
||||
time_t det_startTime;
|
||||
protected:
|
||||
int _function;
|
||||
int det_starting;
|
||||
time_t det_startTime;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
class GirderAxis: public pmacV3Axis {
|
||||
class GirderAxis : public pmacV3Axis {
|
||||
public:
|
||||
GirderAxis(pmacController *pController, int axisNo);
|
||||
asynStatus move(double position, int relative, double min_velocity, double max_velocity, double acceleration);
|
||||
asynStatus move(double position, int relative, double min_velocity,
|
||||
double max_velocity, double acceleration);
|
||||
asynStatus stop(double acceleration);
|
||||
asynStatus poll(bool *moving);
|
||||
|
||||
protected:
|
||||
int IsEnable;
|
||||
|
||||
friend class pmacController;
|
||||
friend class pmacV3Controller;
|
||||
friend class pmacController;
|
||||
friend class pmacV3Controller;
|
||||
};
|
||||
|
||||
#endif /* pmacAxis_H */
|
||||
|
@ -5,8 +5,9 @@ registrar(EL734Register)
|
||||
registrar(PhytronRegister)
|
||||
registrar(EuroMoveRegister)
|
||||
registrar(NanotecRegister)
|
||||
registrar(pmacControllerRegister)
|
||||
registrar(pmacAsynIPPortRegister)
|
||||
# registrar(pmacControllerRegister)
|
||||
registrar(C804ControllerRegister)
|
||||
# registrar(pmacAsynIPPortRegister)
|
||||
registrar(MasterMACSRegister)
|
||||
registrar(SINQControllerRegister)
|
||||
|
||||
|
213
utils/decodeMasterMACStatusR10.py
Executable file
213
utils/decodeMasterMACStatusR10.py
Executable file
@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# List of tuples which encodes the states given in the file description.
|
||||
# Index first with the bit index, then with the bit value
|
||||
interpretation = [
|
||||
("Not ready to be switched on", "Ready to be switched on"), # Bit 0
|
||||
("Not switched on", "Switched on"), # Bit 1
|
||||
("Disabled", "Enabled"), # Bit 2
|
||||
("Ok", "Fault condition set"), # Bit 3
|
||||
("Motor supply voltage absent ", "Motor supply voltage present"), # Bit 4
|
||||
("Motor performs quick stop", "Ok"), # Bit 5
|
||||
("Switch on enabled", "Switch on disabled"), # Bit 6
|
||||
("Ok", "RWarning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7
|
||||
("Motor is idle", "Motor is currently moving"), # Bit 8
|
||||
("Motor does not execute command messages (local mode)", "Motor does execute command messages (remote mode)"), # Bit 9
|
||||
("Target not reached", "Target reached"), # Bit 10
|
||||
("Ok", "Internal limit active"), # Bit 11
|
||||
("Not specified", "Not specified"), # Bit 12
|
||||
("Not specified", "Not specified"), # Bit 13
|
||||
("No event set or event has not occurred yet", "Set event has occurred"), # Bit 14
|
||||
("Axis off (power disabled)", "Axis on (power enabled)"), # Bit 15
|
||||
]
|
||||
|
||||
def decode(value, big_endian: bool = False):
|
||||
|
||||
interpreted = []
|
||||
|
||||
bit_list = [(value >> shift_ind) & 1
|
||||
for shift_ind in range(value.bit_length())] # little endian
|
||||
|
||||
if big_endian:
|
||||
bit_list.reverse() # big endian
|
||||
|
||||
for (bit, interpretations) in zip(bit_list, interpretation):
|
||||
interpreted.append(interpretations[bit])
|
||||
return (bit_list, interpreted)
|
||||
|
||||
def print_decoded(bit_list, interpreted):
|
||||
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
|
||||
print(f"Bit {idx} = {bit_value}: {msg}")
|
||||
|
||||
def interactive():
|
||||
|
||||
# Imported here, because curses is not available in Windows. Using the
|
||||
# interactive mode therefore fails on Windows, but at least the single
|
||||
# command mode can be used (which would not be possible if we would import
|
||||
# curses at the top level)
|
||||
import curses
|
||||
|
||||
stdscr = curses.initscr()
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
stdscr.keypad(True)
|
||||
stdscr.scrollok(True)
|
||||
|
||||
stdscr.addstr(">> ")
|
||||
stdscr.refresh()
|
||||
|
||||
history = [""]
|
||||
ptr = len(history) - 1
|
||||
|
||||
while True:
|
||||
c = stdscr.getch()
|
||||
if c == curses.KEY_RIGHT:
|
||||
(y, x) = stdscr.getyx()
|
||||
if x < len(history[ptr]) + 3:
|
||||
stdscr.move(y, x+1)
|
||||
stdscr.refresh()
|
||||
elif c == curses.KEY_LEFT:
|
||||
(y, x) = stdscr.getyx()
|
||||
if x > 3:
|
||||
stdscr.move(y, x-1)
|
||||
stdscr.refresh()
|
||||
elif c == curses.KEY_UP:
|
||||
if ptr > 0:
|
||||
ptr -= 1
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
elif c == curses.KEY_DOWN:
|
||||
if ptr < len(history) - 1:
|
||||
ptr += 1
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'):
|
||||
if history[ptr] == 'quit':
|
||||
break
|
||||
|
||||
# because of arrow keys move back to the end of the line
|
||||
(y, x) = stdscr.getyx()
|
||||
stdscr.move(y, 3+len(history[ptr]))
|
||||
|
||||
if history[ptr]:
|
||||
result = interpret_inputs(history[ptr].split())
|
||||
if result is None:
|
||||
stdscr.addstr(f"\nBAD INPUT: Expected input of 'value [big_endian]', where 'value' is an int or a float and 'big_endian' is an optional boolean argument.")
|
||||
else:
|
||||
(arg, big_endian) = result
|
||||
(bit_list, interpreted) = decode(arg, big_endian)
|
||||
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
|
||||
stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}")
|
||||
stdscr.refresh()
|
||||
|
||||
if ptr == len(history) - 1 and history[ptr] != "":
|
||||
history += [""]
|
||||
else:
|
||||
history[-1] = ""
|
||||
ptr = len(history) - 1
|
||||
|
||||
stdscr.addstr("\n>> ")
|
||||
stdscr.refresh()
|
||||
|
||||
else:
|
||||
if ptr < len(history) - 1: # Modifying previous input
|
||||
if len(history[-1]) == 0:
|
||||
history[-1] = history[ptr]
|
||||
ptr = len(history) - 1
|
||||
|
||||
else:
|
||||
history += [history[ptr]]
|
||||
ptr = len(history) - 1
|
||||
|
||||
if c == curses.KEY_BACKSPACE:
|
||||
if len(history[ptr]) == 0:
|
||||
continue
|
||||
(y, x) = stdscr.getyx()
|
||||
history[ptr] = history[ptr][0:x-4] + history[ptr][x-3:]
|
||||
stdscr.addch("\r")
|
||||
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:]
|
||||
stdscr.addch("\r")
|
||||
stdscr.clrtoeol()
|
||||
stdscr.addstr(">> " + history[ptr])
|
||||
stdscr.move(y, x+1)
|
||||
stdscr.refresh()
|
||||
|
||||
# to quit
|
||||
curses.nocbreak()
|
||||
stdscr.keypad(False)
|
||||
curses.echo()
|
||||
curses.endwin()
|
||||
|
||||
def interpret_inputs(inputs):
|
||||
|
||||
number = None
|
||||
big_endian = False
|
||||
try:
|
||||
number = int(float(inputs[0]))
|
||||
if len(inputs) > 1:
|
||||
second_arg = inputs[1]
|
||||
if second_arg == "True" or second_arg == "true":
|
||||
big_endian = True
|
||||
elif second_arg == "False" or second_arg == "false":
|
||||
big_endian = False
|
||||
else:
|
||||
big_endian = bool(int(second_arg))
|
||||
return (number, big_endian)
|
||||
except:
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
from sys import argv
|
||||
|
||||
if len(argv) == 1:
|
||||
# Start interactive mode
|
||||
interactive()
|
||||
else:
|
||||
|
||||
result = interpret_inputs(argv[1:])
|
||||
|
||||
if result is None:
|
||||
print("""
|
||||
Decode R10 message of MasterMACs
|
||||
------------------
|
||||
|
||||
MasterMACs returns its status message (R10) as a floating-point number.
|
||||
The bits of this float encode different states. These states are stored
|
||||
in the interpretation variable.
|
||||
|
||||
This script can be used in two different ways:
|
||||
|
||||
Option 1: Single Command
|
||||
------------------------
|
||||
|
||||
Usage: decodeMasterMACStatusR10.py value [big_endian]
|
||||
|
||||
'value' is the return value of a R10 command. This value is interpreted
|
||||
bit-wise and the result is printed out. The optional second argument can
|
||||
be used to specify whether the input value needs to be interpreted as
|
||||
little or big endian. Default is False.
|
||||
|
||||
Option 2: CLI Mode
|
||||
------------------
|
||||
|
||||
Usage: decodeMasterMACStatusR10.py
|
||||
|
||||
A prompt will be opened. Type in the return value of a R10 command, hit
|
||||
enter and the interpretation will be printed in the prompt. After that,
|
||||
the next value can be typed in. Type 'quit' to close the prompt.
|
||||
""")
|
||||
else:
|
||||
print("Motor status")
|
||||
print("============")
|
||||
(arg, big_endian) = result
|
||||
(bit_list, interpreted) = decode(arg, big_endian)
|
||||
print_decoded(bit_list, interpreted)
|
@ -64,6 +64,7 @@ if __name__ == "__main__":
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
stdscr.keypad(True)
|
||||
stdscr.scrollok(True)
|
||||
|
||||
stdscr.addstr(">> ")
|
||||
stdscr.refresh()
|
||||
|
Reference in New Issue
Block a user