1 Commits
1.5.2 ... 1.0.1

Author SHA1 Message Date
847f40970f Bugfix for 1.0 2025-06-17 16:32:59 +02:00
15 changed files with 825 additions and 909 deletions

View File

@@ -1,24 +0,0 @@
name: Test And Build
on: [push]
jobs:
Lint:
runs-on: linepics
steps:
- name: checkout repo
uses: actions/checkout@v4
- name: cppcheck
run: cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 src/*.cpp
- name: formatting
run: clang-format --style=file --Werror --dry-run src/*.cpp
Build:
runs-on: linepics
steps:
- name: checkout repo
uses: actions/checkout@v4
with:
submodules: 'true'
- run: |
sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
echo -e "\nIGNORE_SUBMODULES += sinqmotor" >> Makefile
make install

4
.gitignore vendored
View File

@@ -1,4 +0,0 @@
O.*
.cvsignore
.vscode
utils/__pycache__

48
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,48 @@
default:
image: docker.psi.ch:5000/sinqdev/sinqepics:latest
stages:
- lint
- build
- test
cppcheck:
stage: lint
script:
- cppcheck --std=c++17 --addon=cert --addon=misc --suppress=cert-STR07-C --error-exitcode=1 src/*.cpp
artifacts:
expire_in: 1 week
tags:
- sinq
formatting:
stage: lint
script:
- clang-format --style=file --Werror --dry-run src/*.cpp
artifacts:
expire_in: 1 week
tags:
- sinq
build_module:
stage: build
script:
- export SINQMOTOR_VERSION="$(grep 'sinqMotor_VERSION=' Makefile | cut -d= -f2)"
- git clone --depth 1 --branch "${SINQMOTOR_VERSION}" https://gitlab-ci-token:${CI_JOB_TOKEN}@git.psi.ch/sinq-epics-modules/sinqmotor.git
- pushd sinqmotor
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
- echo "LIBVERSION=${SINQMOTOR_VERSION}" >> Makefile
- make install
- popd
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
- echo "LIBVERSION=${CI_COMMIT_TAG:-0.0.1}" >> Makefile
- make install
- cp -rT "/ioc/modules/masterMacs/$(ls -U /ioc/modules/masterMacs/ | head -1)" "./masterMacs-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
artifacts:
name: "masterMacs-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
paths:
- "masterMacs-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}/*"
expire_in: 1 week
when: always
tags:
- sinq

View File

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

Binary file not shown.

View File

@@ -17,50 +17,23 @@ The folder "utils" contains utility scripts for working with masterMacs motor co
- decodeError.py: Take the return message of a R11 (read error) command and print it in human-readable form.
- writeRead.py: Send messages to the controller and receive answers.
These scripts can be run from anywhere. On Linux, the shebang (#!) automatically
calls the system Python 3 executable:
## Developer guide
```bash
# To show the help, use either flag -h or --help (works on all scripts)
/path/to/mastermacs_repo/utils/decodeStatus.py -h
/path/to/mastermacs_repo/utils/decodeError.py --help
/path/to/mastermacs_repo/utils/writeRead.py -h
### Usage in IOC shell
# To run in non-interactive mode, give the value as an argument
/path/to/mastermacs_repo/utils/decodeStatus.py 1234
/path/to/mastermacs_repo/utils/decodeError.py 5678
/path/to/mastermacs_repo/utils/writeRead.py "R11"
# To run in interactive mode, don't give any argument. This only works on Linux
/path/to/mastermacs_repo/utils/decodeStatus.py
/path/to/mastermacs_repo/utils/decodeError.py
/path/to/mastermacs_repo/utils/writeRead.py
```
To use these scripts on Windows, prefix the Python 3 executable:
```bash
C:/path/to/python3.exe C:/path/to/mastermacs_repo/utils/decodeStatus.py 1234
```
### IOC startup script
masterMacs exposes the following IOC shell functions:
masterMacs exposes the following IOC shell functions (all in masterMacsController.cpp):
- `masterMacsController`: Create a new controller object.
- `masterMacsAxis`: Create a new axis object.
The full masterMacsX.cmd file looks like this:
The full mcu.cmd file looks like this:
```bash
```
# Define the name of the controller and the corresponding port
epicsEnvSet("NAME","mcu")
epicsEnvSet("ASYN_PORT","p$(NAME)")
# Create the TCP/IP socket used to talk with the controller. The socket can be
# adressed from within the IOC shell via the port name
# We do not use the standard asyn port driver here, but a PMAC-specific one
# which enables the usage of StreamDevices for
# communicating with the controller directly.
masterMacsAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
# Create the TCP/IP socket used to talk with the controller. The socket can be adressed from within the IOC shell via the port name
drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
# Create the controller object with the defined name and connect it to the socket via the port name.
# The other parameters are as follows:
@@ -82,45 +55,17 @@ setMaxSubsequentTimeouts("$(NAME)", 20);
setThresholdComTimeout("$(NAME)", 100, 1);
# Parametrize the EPICS record database with the substitution file named after the MCU.
epicsEnvSet("SINQDBPATH","$(masterMacs_DB)/sinqMotor.db")
epicsEnvSet("SINQDBPATH","$(sinqMotor_DB)/sinqMotor.db")
dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
epicsEnvSet("SINQDBPATH","$(masterMacs_DB)/masterMacs.db")
dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
dbLoadRecords("$(masterMacs_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)")
dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)")
```
## Developer guide
### Versioning
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
### Firmware version checking
This driver expects a certain version of the firmware running on the controller itself.
This is checked at IOC startup by reading the version directly from the hardware.
If the firmware version is incompatible to the driver, the IOC will be shut down.
If the firmware version cannot be read (e.g. because the variable used to do so
does not exist yet on old firmware versions), the firmware is assumed to be compatible
to the driver.
The version check is separated into a check of the major and the minor firmware
version against expected values. The firmware is seen as compatible if the following conditions hold:
- Read-out major version == Expected major version
- Read-out read major version >= Expected minor version
The expected versions are defined via compiler flags in `Makefile`:
```
USR_CXXFLAGS += -DFIRMWARE_MAJOR_VERSION=1 -DFIRMWARE_MINOR_VERSION=0
```
Be aware that these flags are only used to compile C++-files (.cpp, .cxx) and not
C-files (.c). For C-files, the Makefile variable `USR_CFLAGS` must be used.
In order to disable the checks, the flags can be set to -1 or just be removed
entirely. If one of the flags is not given, both the major and the minor version
checks are deactivated.
### How to build it
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.

View File

@@ -1,17 +0,0 @@
# Call the nodeReset function of the corresponding masterMacsAxis.
# This record is coupled to the parameter library via nodeReset_ -> NODE_RESET.
record(longout, "$(INSTR)$(M):NodeReset") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) NODE_RESET")
field(PINI, "NO")
}
# Overrides the default value for the "Connected" record provided by sinqMotor.
record(longin, "$(INSTR)$(M):Connected")
{
field(DTYP, "asynInt32")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_CONNECTED")
field(SCAN, "I/O Intr")
field(PINI, "NO")
field(VAL, "0")
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,16 @@
#ifndef masterMacsAXIS_H
#define masterMacsAXIS_H
#include "masterMacsController.h"
#include "sinqAxis.h"
#include <memory>
struct HIDDEN masterMacsAxisImpl;
// Forward declaration of the controller class to resolve the cyclic dependency
// between the controller and the axis .h-file. See
// https://en.cppreference.com/w/cpp/language/class.
class masterMacsController;
class HIDDEN masterMacsAxis : public sinqAxis {
struct masterMacsAxisImpl;
class masterMacsAxis : public sinqAxis {
public:
/**
* @brief Construct a new masterMacsAxis
@@ -16,13 +20,6 @@ class HIDDEN masterMacsAxis : public sinqAxis {
*/
masterMacsAxis(masterMacsController *pController, int axisNo);
/**
* @brief Delete the copy and copy assignment constructors, because this
* class should not be copied (it is tied to hardware!)
*/
masterMacsAxis(const masterMacsAxis &) = delete;
masterMacsAxis &operator=(const masterMacsAxis &) = delete;
/**
* @brief Destroy the masterMacsAxis
*
@@ -57,13 +54,13 @@ class HIDDEN masterMacsAxis : public sinqAxis {
*
* @param position
* @param relative
* @param minVelocity
* @param maxVelocity
* @param min_velocity
* @param max_velocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double minVelocity,
double maxVelocity, double acceleration);
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration);
/**
* @brief Implementation of the `stop` function from asynMotorAxis
@@ -82,27 +79,12 @@ class HIDDEN masterMacsAxis : public sinqAxis {
*/
asynStatus doReset();
/**
* @brief Performs a "node reset" on the axis as defined in the CANopen
* standard
*
* A "node reset" is a factory reset on the axis which completely deletes
* all configured information (e.g. limits or speed) from the axis. The
* MasterMACS controller then reapplies the initial configuration to this
* axis. It can therefore be seen as a "hard" version of the normal error
* reset performed by the `doReset` method.
*
* @return asynStatus
*/
asynStatus nodeReset();
/**
* @brief Readout of some values from the controller at IOC startup
*
* The following steps are performed:
* - Read out the motor status, motor position, velocity and
* acceleration from the MCU and store this information in the parameter
* library.
* - Read out the motor status, motor position, velocity and acceleration
* from the MCU and store this information in the parameter library.
* - Set the enable PV accordint to the initial status of the axis.
*
* @return asynStatus
@@ -118,8 +100,8 @@ class HIDDEN masterMacsAxis : public sinqAxis {
asynStatus enable(bool on);
/**
* @brief Read the encoder type (incremental or absolute) for this axis
* from the MCU and store the information in the PV ENCODER_TYPE.
* @brief Read the encoder type (incremental or absolute) for this axis from
* the MCU and store the information in the PV ENCODER_TYPE.
*
* @return asynStatus
*/
@@ -134,205 +116,173 @@ class HIDDEN masterMacsAxis : public sinqAxis {
bool needInit();
/**
* @brief Instruct the axis to run its init() function during the next
* poll
* @brief Instruct the axis to run its init() function during the next poll
*
* @param needInit
*/
void setNeedInit(bool needInit);
/**
* @brief Return a pointer to the axis controller
*/
virtual masterMacsController *pController() override { return pC_; };
asynStatus readConfig();
/**
* @brief Read the Master MACS status with the xR10 command and store
* the result in axisStatus (see masterMacsAxisImpl redefinition in
* masterMacsAxis.cpp)
* @brief Read the Master MACS status with the xR10 command and store the
* result in axisStatus_
*
*/
asynStatus readAxisStatus();
/*
The functions below read the specified status bit from the axisStatus (see
masterMacsAxisImpl redefinition in masterMacsAxis.cpp) bitset. Since a bit
can either be 0 or 1, the return value is given as a boolean.
The functions below read the specified status bit from the axisStatus
bitset. Since a bit can either be 0 or 1, the return value is given as a
boolean.
*/
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool readyToBeSwitchedOn();
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool switchedOn();
// Bit 2 is unused
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool faultConditionSet();
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool voltagePresent();
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool quickStopping();
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool switchOnDisabled();
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool warning();
// Bit 8 is unused
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool remoteMode();
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool targetReached();
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool internalLimitActive();
// Bits 12 and 13 are unused
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool setEventHasOcurred();
/**
* @brief Read the property from axisStatus (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisStatus_
*/
bool powerEnabled();
/**
* @brief Read the Master MACS error with the xR10 command and store
* the result in axisError (see masterMacsAxisImpl redefinition in
* masterMacsAxis.cpp)
* @brief Read the Master MACS status with the xR10 command and store the
* result in axisStatus_
*
*/
asynStatus readAxisError();
/*
The functions below read the specified error bit from the axisError (see
masterMacsAxisImpl redefinition in masterMacsAxis.cpp) bitset. Since a bit
can either be 0 or 1, the return value is given as a boolean.
The functions below read the specified error bit from the axisError_
bitset. Since a bit can either be 0 or 1, the return value is given as a
boolean.
*/
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool shortCircuit();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool encoderError();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool followingError();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool communicationError();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool feedbackError();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool positiveLimitSwitch();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool negativeLimitSwitch();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool positiveSoftwareLimit();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool negativeSoftwareLimit();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool overCurrent();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool overTemperature();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool overVoltage();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool underVoltage();
/**
* @brief Read the property from axisError (see masterMacsAxisImpl
* redefinition in masterMacsAxis.cpp)
* @brief Read the property from axisError_
*/
bool stoFault();

View File

@@ -12,33 +12,8 @@
#include <string>
#include <unistd.h>
/*
These functions are used to read out the compiler flags defining the major and
minor versions. See README.md, section "Firmware version checking" for
details. If these flags are not given, a default value of -1 is used, which
disables the version checks (it suffices to have one of these at -1 to disable
both major and minor version check)
*/
constexpr int firmware_major_version() {
#ifdef FIRMWARE_MAJOR_VERSION
return FIRMWARE_MAJOR_VERSION;
#else
return -1;
#endif
}
constexpr int firmware_minor_version() {
#ifdef FIRMWARE_MINOR_VERSION
return FIRMWARE_MINOR_VERSION;
#else
return -1;
#endif
}
struct masterMacsControllerImpl {
double comTimeout;
// Indices of additional ParamLib entries
int nodeReset;
};
/**
@@ -81,32 +56,17 @@ masterMacsController::masterMacsController(const char *portName,
: sinqController(portName, ipPortConfigName, numAxes, movingPollPeriod,
idlePollPeriod,
// No additional parameter library entries
0),
pMasterMacsC_(
std::make_unique<masterMacsControllerImpl>((masterMacsControllerImpl){
.comTimeout = comTimeout,
.nodeReset = 0, // Overwritten later
}))
0)
{
// Initialization of local variables
asynStatus status = asynSuccess;
char response[MAXBUF_] = {0};
// =========================================================================
// Create additional parameter library entries
status =
createParam("NODE_RESET", asynParamInt32, &pMasterMacsC_->nodeReset);
if (status != asynSuccess) {
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
pMasterMacsC_ =
std::make_unique<masterMacsControllerImpl>((masterMacsControllerImpl){
.comTimeout = comTimeout,
});
// =========================================================================
@@ -139,60 +99,6 @@ masterMacsController::masterMacsController(const char *portName,
pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort());
exit(-1);
}
// =========================================================================
if (firmware_major_version() >= 0 && firmware_minor_version() >= 0) {
// Check the firmware version according to the conditions outlined in
// README.md
status = read(0, 99, response);
if (status == asynSuccess) {
// Just interpret the version if the variable already exists
double versionRaw = 0.0;
int nvals = sscanf(response, "%lf", &versionRaw);
if (nvals == 1 && versionRaw != 0.0) {
// Discard decimal part
long long versionInt = (long long)versionRaw;
// Extract bugfix (last 3 digits)
// Currently not used, just here for completions sake
// int bugfix = versionInt % 1000;
versionInt /= 1000;
// Extract minor (next 3 digits)
int minor = versionInt % 1000;
versionInt /= 1000;
// Remaining is major
int major = (int)versionInt;
// Compare to target values
if (firmware_major_version() != major ||
firmware_minor_version() > minor) {
asynPrint(this->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR "
"(Incorrect "
"version number of firmware: Expected major "
"version equal "
"to %d, got %d. Expected minor version equal to "
"or larger "
"than %d, got %d).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
firmware_major_version(), major,
firmware_minor_version(), minor);
exit(-1);
}
}
} else {
asynPrint(
this->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nCould not read firmware "
"version\n",
portName, __PRETTY_FUNCTION__, __LINE__);
}
}
}
/*
@@ -214,24 +120,8 @@ masterMacsAxis *masterMacsController::getMasterMacsAxis(int axisNo) {
return dynamic_cast<masterMacsAxis *>(asynAxis);
}
asynStatus masterMacsController::writeInt32(asynUser *pasynUser,
epicsInt32 value) {
int function = pasynUser->reason;
// =====================================================================
masterMacsAxis *axis = getMasterMacsAxis(pasynUser);
// Handle custom PVs
if (function == nodeReset()) {
return axis->nodeReset();
} else {
return sinqController::writeInt32(pasynUser, value);
}
}
asynStatus masterMacsController::read(int axisNo, int tcpCmd, char *response,
double /*comTimeout*/) {
double comTimeout) {
return writeRead(axisNo, tcpCmd, NULL, response);
}
@@ -246,6 +136,7 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
// Definition of local variables.
asynStatus status = asynSuccess;
asynStatus pl_status = asynSuccess;
char fullCommand[MAXBUF_] = {0};
char fullResponse[MAXBUF_] = {0};
char drvMessageText[MAXBUF_] = {0};
@@ -276,6 +167,12 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
comTimeout = pMasterMacsC_->comTimeout;
}
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) {
// We already did the error logging directly in getAxis
return asynError;
}
// Build the full command depending on the inputs to this function
if (isRead) {
snprintf(fullCommand, MAXBUF_ - 1, "%dR%02d\x0D", axisNo, tcpCmd);
@@ -357,38 +254,54 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
}
// Log the overall status (communication successfull or not)
if (axisNo != 0) {
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) {
// We already did the error logging directly in getAxis
return asynError;
if (status == asynSuccess) {
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
} else {
/*
Since the communication failed, there is the possibility that the
controller is not connected at all to the network. In that case, we
cannot be sure that the information read out in the init method of the
axis is still up-to-date the next time we get a connection. Therefore,
an info flag is set which the axis object can use at the start of its
poll method to try to initialize itself.
*/
axis->setNeedInit(true);
/*
Check if the axis already is in an error communication mode. If
it is not, upstream the error. This is done to avoid "flooding"
the user with different error messages if more than one error
ocurred before an error-free communication
*/
pl_status =
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem", axisNo,
__PRETTY_FUNCTION__, __LINE__);
}
if (status == asynSuccess) {
setAxisParamChecked(axis, motorStatusCommsError, false);
} else {
/*
Since the communication failed, there is the possibility that the
controller is not connected at all to the network. In that case, we
cannot be sure that the information read out in the init method of
the axis is still up-to-date the next time we get a connection.
Therefore, an info flag is set which the axis object can use at the
start of its poll method to try to initialize itself.
*/
axis->setNeedInit(true);
if (motorStatusProblem == 0) {
pl_status =
axis->setStringParam(motorMessageText(), drvMessageText);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText",
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
/*
Check if the axis already is in an error communication mode. If
it is not, upstream the error. This is done to avoid "flooding"
the user with different error messages if more than one error
ocurred before an error-free communication
*/
getAxisParamChecked(axis, motorStatusProblem, &motorStatusProblem);
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem",
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
if (motorStatusProblem == 0) {
setAxisParamChecked(axis, motorMessageText, drvMessageText);
setAxisParamChecked(axis, motorStatusProblem, true);
setAxisParamChecked(axis, motorStatusCommsError, false);
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
}
}
@@ -412,23 +325,22 @@ asynStatus masterMacsController::parseResponse(
bool responseValid = false;
int responseStart = 0;
asynStatus status = asynSuccess;
int prevConnected = 1;
int prevConnected = 0;
char printableCommand[MAXBUF_] = {0};
char printableResponse[MAXBUF_] = {0};
msgPrintControlKey parseKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axisNo != 0 && axis == nullptr) {
return asynError;
}
// Was the motor previously connected?
if (axis != nullptr) {
getAxisParamChecked(axis, motorConnected, &prevConnected);
status = getIntegerParam(axisNo, motorConnected(), &prevConnected);
if (status != asynSuccess) {
return paramLibAccessFailed(status, "motorConnected", axisNo,
__PRETTY_FUNCTION__, __LINE__);
}
// We don't use strlen here since the C string terminator 0x00
// occurs in the middle of the char array.
for (uint32_t i = 0; i < MAXBUF_; i++) {
if (fullResponse[i] == '\x19') {
responseStart = i;
@@ -449,17 +361,23 @@ asynStatus masterMacsController::parseResponse(
"connected.\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (axis != nullptr) {
setAxisParamChecked(axis, motorConnected, true);
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__,
__LINE__);
return status;
}
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) {
return asynError;
}
status = axis->setIntegerParam(motorConnected(), 1);
if (status != asynSuccess) {
return paramLibAccessFailed(status, "motorConnected",
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return status;
}
}
@@ -480,17 +398,23 @@ asynStatus masterMacsController::parseResponse(
"disconnected.\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (axis != nullptr) {
setAxisParamChecked(axis, motorConnected, false);
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__,
__LINE__);
return status;
}
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) {
return asynError;
}
status = axis->setIntegerParam(motorConnected(), 0);
if (status != asynSuccess) {
return paramLibAccessFailed(status, "motorConnected",
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return status;
}
}
break;
@@ -576,8 +500,6 @@ asynStatus masterMacsController::readInt32(asynUser *pasynUser,
double masterMacsController::comTimeout() { return pMasterMacsC_->comTimeout; }
int masterMacsController::nodeReset() { return pMasterMacsC_->nodeReset; }
/***************************************************************************/
/** The following functions are C-wrappers, and can be called directly from
* iocsh */
@@ -637,8 +559,7 @@ static const iocshArg *const CreateControllerArgs[] = {
&CreateControllerArg0, &CreateControllerArg1, &CreateControllerArg2,
&CreateControllerArg3, &CreateControllerArg4, &CreateControllerArg5};
static const iocshFuncDef configMasterMacsCreateController = {
"masterMacsController", 6, CreateControllerArgs,
"Create a new instance of a MasterMACS controller."};
"masterMacsController", 6, CreateControllerArgs};
static void configMasterMacsCreateControllerCallFunc(const iocshArgBuf *args) {
masterMacsCreateController(args[0].sval, args[1].sval, args[2].ival,
args[3].dval, args[4].dval, args[5].dval);

View File

@@ -8,18 +8,14 @@
#ifndef masterMacsController_H
#define masterMacsController_H
#include "masterMacsAxis.h"
#include "sinqAxis.h"
#include "sinqController.h"
#include <memory>
// Forward declaration of the controller class to resolve the cyclic dependency
// between the controller and the axis .h-file. See
// https://en.cppreference.com/w/cpp/language/class.
class HIDDEN masterMacsAxis;
struct masterMacsControllerImpl;
struct HIDDEN masterMacsControllerImpl;
class HIDDEN masterMacsController : public sinqController {
class masterMacsController : public sinqController {
public:
/**
@@ -38,13 +34,6 @@ class HIDDEN masterMacsController : public sinqController {
int numAxes, double movingPollPeriod,
double idlePollPeriod, double comTimeout);
/**
* @brief Delete the copy and copy assignment constructors, because this
* class should not be copied (it is tied to hardware!)
*/
masterMacsController(const masterMacsController &) = delete;
masterMacsController &operator=(const masterMacsController &) = delete;
/**
* @brief Overloaded version of the sinqController version
*
@@ -72,17 +61,6 @@ class HIDDEN masterMacsController : public sinqController {
*/
masterMacsAxis *getMasterMacsAxis(int axisNo);
/**
* @brief Overloaded function of sinqController
*
* The function is overloaded to allow resetting the node
*
* @param pasynUser Specify the axis via the asynUser
* @param value New value
* @return asynStatus
*/
virtual asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
/**
* @brief Send a command to the hardware (S mode)
*
@@ -159,9 +137,6 @@ class HIDDEN masterMacsController : public sinqController {
*/
double comTimeout();
// Accessors for additional PVs
int nodeReset();
private:
std::unique_ptr<masterMacsControllerImpl> pMasterMacsC_;
};

View File

@@ -9,82 +9,73 @@ To read the manual, simply run this script without any arguments.
Stefan Mathis, January 2025
"""
import platform
from decodeCommon import interactive, decode, print_decoded
# 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 specified", "Not specified"), # Bit 0
("Ok", "Short circuit"), # Bit 1
("Ok", "Encoder error"), # Bit 2
("Ok", "Following error"), # Bit 3
("Ok", "Communication error"), # Bit 4
("Ok", "Feedback error"), # Bit 5
("Ok", "Positive limit switch hit"), # Bit 6
("Ok", "Negative limit switch hit"), # Bit 7
("Ok", "Positive software limit hit"), # Bit 8
("Ok", "Negative software limit hit"), # Bit 9
("Ok", "Over-current"), # Bit 10
("Ok", "Over-temperature drive"), # Bit 11
("Ok", "Over-voltage"), # Bit 12
("Ok", "Under-voltage"), # Bit 13
("Not specified", "Not specified"), # Bit 14
("Ok", "STO fault (STO input is on disable state)"), # Bit 15
("Not specified", "Not specified"), # Bit 0
("Ok", "Short circuit"), # Bit 1
("Ok", "Encoder error"), # Bit 2
("Ok", "Following error"), # Bit 3
("Ok", "Communication error"), # Bit 4
("Ok", "Feedback error"), # Bit 5
("Ok", "Positive limit switch hit"), # Bit 6
("Ok", "Negative limit switch hit"), # Bit 7
("Ok", "Positive software limit hit"), # Bit 8
("Ok", "Negative software limit hit"), # Bit 9
("Ok", "Over-current"), # Bit 10
("Ok", "Over-temperature drive"), # Bit 11
("Ok", "Over-voltage"), # Bit 12
("Ok", "Under-voltage"), # Bit 13
("Not specified", "Not specified"), # Bit 14
("Ok", "STO fault (STO input is on disable state)"), # Bit 15
]
help = """
Decode R11 message of MasterMACs
------------------
MasterMACs returns its error message (R11) 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: decodeError.py value
'value' is the return value of a R11 command. This value is interpreted
bit-wise and the result is printed out.
Option 2: CLI Mode (Linux-only)
-------------------------------
Usage: decodeError.py
ONLY AVAILABLE ON LINUX!
A prompt will be opened. Type in the return value of a R11 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.
"""
if __name__ == "__main__":
from sys import argv
if "-h" or "--help" in argv:
print(help)
if len(argv) == 1:
# Start interactive mode
if platform.system() == "Linux":
interactive()
else:
print(help)
interactive()
else:
number = None
try:
number = int(float(argv[1]))
except:
print(help)
print("""
Decode R11 message of MasterMACs
------------------
MasterMACs returns its error message (R11) 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: decodeError.py value
'value' is the return value of a R11 command. This value is interpreted
bit-wise and the result is printed out.
Option 2: CLI Mode
------------------
Usage: decodeError.py
A prompt will be opened. Type in the return value of a R11 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.
""")
if number is not None:
print("Motor error")
print("===========")
print("============")
(bit_list, interpreted) = decode(number, interpretation)
print_decoded(bit_list, interpreted)

View File

@@ -9,81 +9,71 @@ To read the manual, simply run this script without any arguments.
Stefan Mathis, December 2024
"""
import platform
from decodeCommon import interactive, decode, print_decoded
# 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", "Warning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7
("Not specified", "Not specified"), # 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 (current, voltage, velocity or position)"), # Bit 11
("Not specified", "Not specified"), # Bit 12
("Not specified", "Not specified"), # Bit 13
("Not specified", "Not specified"), # Bit 14
("Not specified", "Not specified"), # Bit 15
("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", "Warning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7
("Not specified", "Not specified"), # 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 (current, voltage, velocity or position)"), # Bit 11
("Not specified", "Not specified"), # Bit 12
("Not specified", "Not specified"), # Bit 13
("Not specified", "Not specified"), # Bit 14
("Not specified", "Not specified"), # Bit 15
]
help = """
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: decodeStatus.py value
'value' is the return value of a R10 command. This value is interpreted
bit-wise and the result is printed out.
Option 2: CLI Mode (Linux-only)
-------------------------------
Usage: decodeStatus.py
ONLY AVAILABLE ON LINUX!
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.
"""
if __name__ == "__main__":
from sys import argv
if "-h" or "--help" in argv:
print(help)
if len(argv) == 1:
# Start interactive mode
if platform.system() == "Linux":
interactive()
else:
print(help)
interactive()
else:
number = None
try:
number = int(float(argv[1]))
except:
print(help)
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: decodeStatus.py value
'value' is the return value of a R10 command. This value is interpreted
bit-wise and the result is printed out.
Option 2: CLI Mode
------------------
Usage: decodeStatus.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.
""")
if number is not None:
print("Motor status")
print("============")

View File

@@ -6,72 +6,51 @@ To read the manual, simply run this script without any arguments.
Stefan Mathis, April 2025
"""
import platform
import struct
import socket
help = """
Send commands to and receive replies from MasterMACS controllers
Option 1: Single Command
------------------------
Usage: writeRead.py pmachost:port command
This then returns the response for command.
Option 2: CLI Mode (Linux-only)
-------------------------------
Usage: writeRead.py pmachost:port
ONLY AVAILABLE ON LINUX!
You can then type in a command, hit enter, and the response will see
the reponse, before being prompted to again enter a command. Type
'quit' to close prompt.
"""
import curses
def packMasterMacsCommand(command):
# 0x0D = Carriage return
buf = struct.pack('B', 0x0D)
buf = bytes(command, 'utf-8') + buf
return bytes(command, 'utf-8')
# 0x0D = Carriage return
buf = struct.pack('B',0x0D)
buf = bytes(command,'utf-8') + buf
return bytes(command,'utf-8')
def readMasterMacsReply(input):
msg = bytearray()
expectAck = True
while True:
b = input.recv(1)
bint = int.from_bytes(b, byteorder='little')
if bint == 2 or bint == 7: # STX or BELL
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
if "-h" or "--help" in argv:
print(help)
else:
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) == 2:
if len(argv) == 3:
buf = packMasterMacsCommand(argv[2])
s.send(buf)
reply = readMasterMacsReply(s)
print(reply.decode('utf-8') + '\n')
if platform.system() == "Linux":
import curses
else:
try:
stdscr = curses.initscr()
curses.noecho()
@@ -101,13 +80,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':
@@ -133,7 +112,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
@@ -146,34 +125,47 @@ 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()
# to quit
curses.nocbreak()
stdscr.keypad(False)
curses.echo()
curses.endwin()
else:
print(help)
elif len(argv) == 3:
buf = packMasterMacsCommand(argv[2])
s.send(buf)
reply = readMasterMacsReply(s)
print(reply.decode('utf-8') + '\n')
else:
print(help)
finally:
# to quit
curses.nocbreak()
stdscr.keypad(False)
curses.echo()
curses.endwin()
except:
print("""
Invalid Arguments
Option 1: Single Command
------------------------
Usage: writeRead.py pmachost:port command
This then returns the response for command.
Option 2: CLI Mode
------------------
Usage: writeRead.py pmachost:port
You can then type in a command, hit enter, and the response will see
the reponse, before being prompted to again enter a command. Type
'quit' to close prompt.
""")