36 Commits
0.1.0 ... 1.1.1

Author SHA1 Message Date
a9e08460d9 Updated sinqMotor 2025-06-18 08:17:21 +02:00
37dacbd3ee Updated sinqMotor version and removed unnecessary require 2025-06-17 13:21:59 +02:00
d198dc8c9f Use axisParam accessor macros 2025-06-17 13:21:55 +02:00
13f2ec4fbf Switched to macro-based getters and setters for paramLib 2025-06-17 13:21:08 +02:00
6ccb60f263 Merge pull request 'Remove sinqmotor dep, it's statically linked. specify rhel8 arch' (#1) from makefile-fixes into main
Reviewed-on: #1
2025-06-11 16:52:42 +02:00
b8730e80e0 Remove sinqmotor dep, it's statically linked. specify rhel8 arch 2025-05-28 10:54:59 +02:00
deb6e6996e Ready for release 1.0 2025-05-23 12:29:30 +02:00
d1d694ad6b Applied PIMPL principle 2025-05-23 12:16:25 +02:00
e0a74c5598 Changed sinqMotor version to 0.15.2 2025-05-16 16:15:01 +02:00
c334ed9f04 Add default value for motorMessageText 2025-05-15 12:22:33 +02:00
4b0031c3af Fixed bug with readInt32 function 2025-05-15 12:03:06 +02:00
61335970ce Use latest version of sinqMotor 2025-05-15 11:43:40 +02:00
081a21073b Adjusted Makefile for static linking 2025-05-15 11:34:43 +02:00
f2e8eb2762 Fixed serious bug in sinqMotor 2025-05-15 11:32:35 +02:00
e93f11e779 Adjusted usage of motorMessageText to be an error text only 2025-05-14 16:28:51 +02:00
989410474e Added sinqMotor 0.15.0 as static dependency 2025-05-14 16:20:08 +02:00
8d8561d833 Removed sinqMotor from required
sinqMotor is statically compiled into this driver.
2025-05-14 12:20:15 +02:00
a56a8cf646 Added explanation why the return status of doPoll is not used in the enable function. 2025-05-13 14:46:13 +02:00
cd57409f3c Added motorConnected logic 2025-04-25 15:58:03 +02:00
21ffcba8be Back to dev version 2025-04-22 15:07:45 +02:00
3bfc2414b6 Fixed sinqMotor version for tagging 2025-04-22 15:05:16 +02:00
60053244a1 Fixed moving after enable bug
When a MasterMacs motor is powered for the first time, it does not have
a target set. Therefore, the targetReached bit is 0, which the driver
used to interpret as "moving". This is solved now by an additional flag
which checks if the motor did a handshake.

Additionally, the communication module was simplified and new utility
scripts were added. It is now made sure that the communication timeout
for enabling and sending move commands is now at least equal to
PowerCycleTimeout defined in src/masterMacsAxis.cpp.
2025-04-17 16:50:42 +02:00
699b588ba5 Replaced ipPortUser_ with ipPortAsynOctetSyncIO_
See comment to sinqMotor 0.12.0
2025-04-15 17:22:15 +02:00
e86c517fc7 Bumped required sinqMotor version to 0.11.0 2025-04-10 09:09:27 +02:00
f733718ee7 Using appropriate sinqMotor version 2025-04-09 15:26:45 +02:00
a8c3499dc5 Set required sinqMotor version to mathis_s 2025-04-04 13:31:49 +02:00
fe52245e38 Custom timeout for enable and position methods
Added a custom timeout for the enable command, as it takes quite a bit
of time for the motor controller to answer and we don't want to show a
premature communication timeout error. Also changed the code in order to
use the motorPosition() and setMotorPosition() methods instead of
directly accessing the paramLib.
2025-03-31 10:48:41 +02:00
a3e849f386 Changed to the "motorPosition" and "setMotorPosition" functions provided
by sinqMotor.
2025-03-28 14:53:04 +01:00
16564011a6 Added stop and error reset function for masterMacs 2025-03-19 15:07:09 +01:00
631ee46a50 Removed friend class declaration and replaced access to private,properties with accessors 2025-03-10 17:07:33 +01:00
cf9899062a Modified communication protocol for MCU software 2.0 2025-03-10 14:29:56 +01:00
d975af75a2 Suppress strcpy-related errors on a global level 2025-03-04 14:45:47 +01:00
55aabb0468 Suppress strcpy-linter message 2025-03-04 14:37:04 +01:00
755ae42b3b Added cppcheck-suppress for strcpy - I know strcpy is safe because I
check the length beforehand.
2025-03-04 14:19:19 +01:00
4ad842c097 Added new feature msgPrintControl from sinqMotor 0.8.0.
Correspondingly, the minimum version requirement for sinqMotor has been bumped to 0.8.0.
2025-03-04 12:42:11 +01:00
cb91a8aa36 Added .clang-format file 2025-02-17 09:31:35 +01:00
13 changed files with 1297 additions and 981 deletions

246
.clang-format Normal file
View File

@ -0,0 +1,246 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
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
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: "^ IWYU pragma:"
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: ".*"
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: "(Test)?$"
IncludeIsMainSourceRegex: ""
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
---

View File

@ -1,6 +1,6 @@
default: default:
image: docker.psi.ch:5000/sinqdev/sinqepics:latest image: docker.psi.ch:5000/sinqdev/sinqepics:latest
stages: stages:
- lint - lint
- build - build
@ -9,7 +9,7 @@ stages:
cppcheck: cppcheck:
stage: lint stage: lint
script: script:
- cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 src/*.cpp - cppcheck --std=c++17 --addon=cert --addon=misc --suppress=cert-STR07-C --error-exitcode=1 src/*.cpp
artifacts: artifacts:
expire_in: 1 week expire_in: 1 week
tags: tags:
@ -24,16 +24,6 @@ formatting:
tags: tags:
- sinq - sinq
# clangtidy:
# stage: lint
# 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:
# # - sinq
build_module: build_module:
stage: build stage: build
script: script:

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "sinqMotor"]
path = sinqMotor
url = https://gitea.psi.ch/lin-epics-modules/sinqMotor

View File

@ -6,24 +6,26 @@ BUILDCLASSES=Linux
EPICS_VERSIONS=7.0.7 EPICS_VERSIONS=7.0.7
ARCH_FILTER=RHEL% ARCH_FILTER=RHEL%
# Additional module dependencies
REQUIRED+=sinqMotor
# Specify the version of asynMotor we want to build against # Specify the version of asynMotor we want to build against
motorBase_VERSION=7.2.2 motorBase_VERSION=7.2.2
# Specify the version of sinqMotor we want to build against
sinqMotor_VERSION=0.7.0
# These headers allow to depend on this library for derived drivers. # These headers allow to depend on this library for derived drivers.
HEADERS += src/masterMacsAxis.h HEADERS += src/masterMacsAxis.h
HEADERS += src/masterMacsController.h HEADERS += src/masterMacsController.h
# Source files to build # Source files to build
SOURCES += sinqMotor/src/msgPrintControl.cpp
SOURCES += sinqMotor/src/sinqAxis.cpp
SOURCES += sinqMotor/src/sinqController.cpp
SOURCES += src/masterMacsAxis.cpp SOURCES += src/masterMacsAxis.cpp
SOURCES += src/masterMacsController.cpp SOURCES += src/masterMacsController.cpp
# Store the record files
TEMPLATES += sinqMotor/db/asynRecord.db
TEMPLATES += sinqMotor/db/sinqMotor.db
# This file registers the motor-specific functions in the IOC shell. # This file registers the motor-specific functions in the IOC shell.
DBDS += sinqMotor/src/sinqMotor.dbd
DBDS += src/masterMacs.dbd DBDS += src/masterMacs.dbd
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Wpedantic -Wextra -Werror USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Wextra -Werror

View File

@ -1,15 +1,21 @@
# masterMacs # masterMacs
## <span style="color:red">Please read the documentation of sinqMotor first: https://git.psi.ch/sinq-epics-modules/sinqmotor</span>
## Overview ## Overview
This is a driver for the masterMacs motion controller with the SINQ communication protocol. It is based on the sinqMotor shared library (https://git.psi.ch/sinq-epics-modules/sinqmotor). The header files contain detailed documentation for all public functions. The headers themselves are exported when building the library to allow other drivers to depend on this one. This is a driver for the masterMacs motion controller with the SINQ communication protocol. It is based on the sinqMotor shared library (https://git.psi.ch/sinq-epics-modules/sinqmotor). The header files contain detailed documentation for all public functions. The headers themselves are exported when building the library to allow other drivers to depend on this one.
Compatible to MasterMACS software 2.0.0.
## User guide ## User guide
This driver is a standard sinqMotor-derived driver and does not need any specific configuration. For the general configuration, please see https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md. This driver is a standard sinqMotor-derived driver and does not need any specific configuration. For the general configuration, please see https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
The folder "utils" contains utility scripts for working with masterMacs motor controllers: The folder "utils" contains utility scripts for working with masterMacs motor controllers:
- decodeStatus.py: Take the return message of a R10 (read status) command and print it in human-readable form. - decodeStatus.py: Take the return message of a R10 (read status) command and print it in human-readable form.
- 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.
## Developer guide ## Developer guide
@ -18,22 +24,42 @@ The folder "utils" contains utility scripts for working with masterMacs motor co
masterMacs exposes the following IOC shell functions (all in masterMacsController.cpp): masterMacs exposes the following IOC shell functions (all in masterMacsController.cpp):
- `masterMacsController`: Create a new controller object. - `masterMacsController`: Create a new controller object.
- `masterMacsAxis`: Create a new axis object. - `masterMacsAxis`: Create a new axis object.
These functions are parametrized as follows:
The full mcu.cmd file looks like this:
``` ```
masterMacsController( # Define the name of the controller and the corresponding port
"$(NAME)", # Name of the MCU, e.g. mcu1. This parameter should be provided by an environment variable. epicsEnvSet("NAME","mcu")
"$(ASYN_PORT)", # IP-Port of the MCU. This parameter should be provided by an environment variable. epicsEnvSet("ASYN_PORT","p$(NAME)")
8, # Maximum number of axes
0.05, # Busy poll period in seconds # 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
1, # Idle poll period in seconds drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
0.05 # Communication timeout in seconds
); # Create the controller object with the defined name and connect it to the socket via the port name.
``` # The other parameters are as follows:
``` # 8: Maximum number of axes
masterMacsAxis( # 0.05: Busy poll period in seconds
"$(NAME)", # Name of the associated MCU, e.g. mcu1. This parameter should be provided by an environment variable. # 1: Idle poll period in seconds
1, # Index of the axis. # 1: Socket communication timeout in seconds
); masterMacsController("$(NAME)", "$(ASYN_PORT)", 8, 0.05, 1, 1);
# Define some axes for the specified MCU at the given slot (1, 2 and 5). No slot may be used twice!
masterMacsAxis("$(NAME)",1);
masterMacsAxis("$(NAME)",2);
masterMacsAxis("$(NAME)",5);
# Set the number of subsequent timeouts
setMaxSubsequentTimeouts("$(NAME)", 20);
# Configure the timeout frequency watchdog:
setThresholdComTimeout("$(NAME)", 100, 1);
# Parametrize the EPICS record database with the substitution file named after the MCU.
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("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)")
``` ```
### Versioning ### Versioning

1
sinqMotor Submodule

Submodule sinqMotor added at e618b39687

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,10 @@
#ifndef masterMacsAXIS_H #ifndef masterMacsAXIS_H
#define masterMacsAXIS_H #define masterMacsAXIS_H
#include "masterMacsController.h"
#include "sinqAxis.h" #include "sinqAxis.h"
#include <bitset> #include <memory>
// Forward declaration of the controller class to resolve the cyclic dependency struct masterMacsAxisImpl;
// between C804Controller.h and C804Axis.h. See
// https://en.cppreference.com/w/cpp/language/class.
class masterMacsController;
class masterMacsAxis : public sinqAxis { class masterMacsAxis : public sinqAxis {
public: public:
@ -24,15 +22,6 @@ class masterMacsAxis : public sinqAxis {
*/ */
virtual ~masterMacsAxis(); virtual ~masterMacsAxis();
/**
* @brief Implementation of the `stop` function from asynMotorAxis
*
* @param acceleration Acceleration ACCEL from the motor record. This
* value is currently not used.
* @return asynStatus
*/
asynStatus stop(double acceleration);
/** /**
* @brief Implementation of the `doHome` function from sinqAxis. The * @brief Implementation of the `doHome` function from sinqAxis. The
* parameters are described in the documentation of `sinqAxis::doHome`. * parameters are described in the documentation of `sinqAxis::doHome`.
@ -69,6 +58,23 @@ class masterMacsAxis : public sinqAxis {
asynStatus doMove(double position, int relative, double min_velocity, asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration); double max_velocity, double acceleration);
/**
* @brief Implementation of the `stop` function from asynMotorAxis
*
* @param acceleration Acceleration ACCEL from the motor record. This
* value is currently not used.
* @return asynStatus
*/
asynStatus stop(double acceleration);
/**
* @brief Implementation of the `doReset` function from sinqAxis.
*
* @param on
* @return asynStatus
*/
asynStatus doReset();
/** /**
* @brief Readout of some values from the controller at IOC startup * @brief Readout of some values from the controller at IOC startup
* *
@ -97,20 +103,25 @@ class masterMacsAxis : public sinqAxis {
*/ */
asynStatus readEncoderType(); asynStatus readEncoderType();
protected: /**
masterMacsController *pC_; * @brief Check if the axis needs to run its initialization function
double lastSetSpeed_; *
bool waitForHandshake_; * @return true
time_t timeAtHandshake_; * @return false
*/
bool needInit();
asynStatus readConfig(); /**
* @brief Instruct the axis to run its init() function during the next poll
*
* @param needInit
*/
void setNeedInit(bool needInit);
/* /**
The axis status and axis error of MasterMACS are given as an integer from * @brief Return a pointer to the axis controller
the controller. The 16 individual bits contain the actual information. */
*/ virtual masterMacsController *pController() override { return pC_; };
std::bitset<16> axisStatus_;
std::bitset<16> axisError_;
/** /**
* @brief Read the Master MACS status with the xR10 command and store the * @brief Read the Master MACS status with the xR10 command and store the
@ -120,7 +131,7 @@ class masterMacsAxis : public sinqAxis {
asynStatus readAxisStatus(); asynStatus readAxisStatus();
/* /*
The functions below read the specified status bit from the axisStatus_ 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 bitset. Since a bit can either be 0 or 1, the return value is given as a
boolean. boolean.
*/ */
@ -128,68 +139,68 @@ class masterMacsAxis : public sinqAxis {
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool readyToBeSwitchedOn() { return axisStatus_[0]; } bool readyToBeSwitchedOn();
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool switchedOn() { return axisStatus_[1]; } bool switchedOn();
// Bit 2 is unused // Bit 2 is unused
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool faultConditionSet() { return axisStatus_[3]; } bool faultConditionSet();
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool voltagePresent() { return axisStatus_[4]; } bool voltagePresent();
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool quickStopping() { return axisStatus_[5] == 0; } bool quickStopping();
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool switchOnDisabled() { return axisStatus_[6]; } bool switchOnDisabled();
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool warning() { return axisStatus_[7]; } bool warning();
// Bit 8 is unused // Bit 8 is unused
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool remoteMode() { return axisStatus_[9]; } bool remoteMode();
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool targetReached() { return axisStatus_[10]; } bool targetReached();
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool internalLimitActive() { return axisStatus_[11]; } bool internalLimitActive();
// Bits 12 and 13 are unused // Bits 12 and 13 are unused
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool setEventHasOcurred() { return axisStatus_[14]; } bool setEventHasOcurred();
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool powerEnabled() { return axisStatus_[15]; } bool powerEnabled();
/** /**
* @brief Read the Master MACS status with the xR10 command and store the * @brief Read the Master MACS status with the xR10 command and store the
@ -207,75 +218,76 @@ class masterMacsAxis : public sinqAxis {
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool shortCircuit() { return axisError_[1]; } bool shortCircuit();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool encoderError() { return axisError_[2]; } bool encoderError();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool followingError() { return axisError_[3]; } bool followingError();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool communicationError() { return axisError_[4]; } bool communicationError();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool feedbackError() { return axisError_[5]; } bool feedbackError();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool positiveLimitSwitch() { return axisError_[6]; } bool positiveLimitSwitch();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool negativeLimitSwitch() { return axisError_[7]; } bool negativeLimitSwitch();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool positiveSoftwareLimit() { return axisError_[8]; } bool positiveSoftwareLimit();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool negativeSoftwareLimit() { return axisError_[9]; } bool negativeSoftwareLimit();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool overCurrent() { return axisError_[10]; } bool overCurrent();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool overTemperature() { return axisError_[11]; } bool overTemperature();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool overVoltage() { return axisError_[12]; } bool overVoltage();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool underVoltage() { return axisError_[13]; } bool underVoltage();
/** /**
* @brief Read the property from axisError_ * @brief Read the property from axisError_
*/ */
bool stoFault() { return axisError_[15]; } bool stoFault();
private: private:
friend class masterMacsController; masterMacsController *pC_;
std::unique_ptr<masterMacsAxisImpl> pMasterMacsA_;
}; };
#endif #endif

View File

@ -12,6 +12,10 @@
#include <string> #include <string>
#include <unistd.h> #include <unistd.h>
struct masterMacsControllerImpl {
double comTimeout;
};
/** /**
* @brief Copy src into dst and replace all NULL terminators up to the carriage * @brief Copy src into dst and replace all NULL terminators up to the carriage
* return with spaces. This allows to print *dst with asynPrint. * return with spaces. This allows to print *dst with asynPrint.
@ -49,54 +53,39 @@ masterMacsController::masterMacsController(const char *portName,
int numAxes, double movingPollPeriod, int numAxes, double movingPollPeriod,
double idlePollPeriod, double idlePollPeriod,
double comTimeout) double comTimeout)
: sinqController( : sinqController(portName, ipPortConfigName, numAxes, movingPollPeriod,
portName, ipPortConfigName, numAxes, movingPollPeriod, idlePollPeriod, idlePollPeriod,
/* // No additional parameter library entries
The following parameter library entries are added in this driver: 0)
- REREAD_ENCODER_POSITION
- READ_CONFIG
*/
NUM_masterMacs_DRIVER_PARAMS)
{ {
// Initialization of local variables // Initialization of local variables
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
// Initialization of all member variables pMasterMacsC_ =
lowLevelPortUser_ = nullptr; std::make_unique<masterMacsControllerImpl>((masterMacsControllerImpl){
comTimeout_ = comTimeout; .comTimeout = comTimeout,
});
// =========================================================================; // =========================================================================
/*
We try to connect to the port via the port name provided by the constructor.
If this fails, the function is terminated via exit
*/
pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL);
if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot "
"connect to MCU controller).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__);
exit(-1);
}
/* /*
Define the end-of-string of a message coming from the device to EPICS. Define the end-of-string of a message coming from the device to EPICS.
It is not necessary to append a terminator to outgoing messages, since It is not necessary to append a terminator to outgoing messages, since
the message length is encoded in the message header in the getSetResponse the message length is encoded in the message header.
method.
*/ */
const char *message_from_device = "\x03"; // Hex-code for ETX const char *message_from_device = "\x0D"; // Hex-code for CR
status = pasynOctetSyncIO->setInputEos( status = pasynOctetSyncIO->setInputEos(pasynOctetSyncIOipPort(),
lowLevelPortUser_, message_from_device, strlen(message_from_device)); message_from_device,
strlen(message_from_device));
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (setting " "Controller \"%s\" => %s, line %d:\nFATAL ERROR (setting "
"input EOS failed with %s).\nTerminating IOC", "input EOS failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__, portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status)); stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_); pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort());
exit(-1); exit(-1);
} }
@ -107,7 +96,7 @@ masterMacsController::masterMacsController(const char *portName,
"ParamLib callbacks failed with %s).\nTerminating IOC", "ParamLib callbacks failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__, portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status)); stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_); pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort());
exit(-1); exit(-1);
} }
} }
@ -117,61 +106,38 @@ 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 Axis, a nullptr is returned and an If the axis does not exist or is not a Axis, a nullptr is returned and an
error is emitted. error is emitted.
*/ */
masterMacsAxis *masterMacsController::getAxis(asynUser *pasynUser) { masterMacsAxis *masterMacsController::getMasterMacsAxis(asynUser *pasynUser) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser); asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
return masterMacsController::castToAxis(asynAxis); return dynamic_cast<masterMacsAxis *>(asynAxis);
} }
/* /*
Access one of the axes of the controller via the axis index. Access one of the axes of the controller via the axis index.
If the axis does not exist or is not a Axis, the function must return Null If the axis does not exist or is not a Axis, the function must return Null
*/ */
masterMacsAxis *masterMacsController::getAxis(int axisNo) { masterMacsAxis *masterMacsController::getMasterMacsAxis(int axisNo) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo); asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
return masterMacsController::castToAxis(asynAxis); return dynamic_cast<masterMacsAxis *>(asynAxis);
} }
masterMacsAxis *masterMacsController::castToAxis(asynMotorAxis *asynAxis) { asynStatus masterMacsController::read(int axisNo, int tcpCmd, char *response,
double comTimeout) {
// =========================================================================
// 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 Axis
masterMacsAxis *axis = dynamic_cast<masterMacsAxis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not "
"an instance of masterMacsAxis",
portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__);
}
return axis;
}
asynStatus masterMacsController::read(int axisNo, int tcpCmd, char *response) {
return writeRead(axisNo, tcpCmd, NULL, response); return writeRead(axisNo, tcpCmd, NULL, response);
} }
asynStatus masterMacsController::write(int axisNo, int tcpCmd, asynStatus masterMacsController::write(int axisNo, int tcpCmd,
const char *payload) { const char *payload, double comTimeout) {
return writeRead(axisNo, tcpCmd, payload, NULL); return writeRead(axisNo, tcpCmd, payload, NULL, comTimeout);
} }
asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
const char *payload, const char *payload, char *response,
char *response) { double comTimeout) {
// Definition of local variables. // Definition of local variables.
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
asynStatus pl_status = asynSuccess;
char fullCommand[MAXBUF_] = {0}; char fullCommand[MAXBUF_] = {0};
char fullResponse[MAXBUF_] = {0}; char fullResponse[MAXBUF_] = {0};
char printableCommand[MAXBUF_] = {0};
char printableResponse[MAXBUF_] = {0};
char drvMessageText[MAXBUF_] = {0}; char drvMessageText[MAXBUF_] = {0};
int motorStatusProblem = 0; int motorStatusProblem = 0;
@ -195,121 +161,65 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
// ========================================================================= // =========================================================================
masterMacsAxis *axis = getAxis(axisNo); // Check if a custom timeout has been given
if (comTimeout < 0.0) {
comTimeout = pMasterMacsC_->comTimeout;
}
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) { if (axis == nullptr) {
// We already did the error logging directly in getAxis // We already did the error logging directly in getAxis
return asynError; return asynError;
} }
/* // Build the full command depending on the inputs to this function
PSI SINQ uses a custom protocol which is described in
PSI_TCP_Interface_V1-8.pdf (p. // 4-17).
A special case is the message length, which is specified by two bytes
LSB and MSB: MSB = message length / 256 LSB = message length % 256. For
example, a message length of 47 chars would result in MSB = 0, LSB = 47,
whereas a message length of 356 would result in MSB = 1, LSB = 100.
The full protocol looks as follows:
0x05 -> Start of protocol frame ENQ
[LSB]
[MSB]
0x19 -> Data type PDO1
value [Actual message] It is not necessary to append a terminator, since
this protocol encodes the message length in LSB and MSB.
0x0D -> Carriage return (ASCII alias \r)
0x03 -> End of text ETX
*/
fullCommand[0] = '\x05'; // ENQ
fullCommand[1] = 1; // Placeholder value, can be anything other than 0
fullCommand[2] = 1; // Placeholder value, can be anything other than 0
fullCommand[3] = '\x19'; // PD01
// Create the command and add CR and ETX at the end
if (isRead) { if (isRead) {
snprintf(&fullCommand[4], MAXBUF_ - 4, "%dR%02d\x0D\x03", axisNo, snprintf(fullCommand, MAXBUF_ - 1, "%dR%02d\x0D", axisNo, tcpCmd);
tcpCmd);
} else { } else {
snprintf(&fullCommand[4], MAXBUF_ - 4, "%dS%02d=%s\x0D\x03", axisNo, if (strlen(payload) == 0) {
tcpCmd, payload); snprintf(fullCommand, MAXBUF_ - 1, "%dS%02d\x0D", axisNo, tcpCmd);
} else {
snprintf(fullCommand, MAXBUF_ - 1, "%dS%02d=%s\x0D", axisNo, tcpCmd,
payload);
}
} }
// Calculate the command length // Calculate the command length
const size_t fullCommandLength = strlen(fullCommand); const size_t fullCommandLength = strlen(fullCommand);
// Length of the command without ENQ and ETX // Flush the IOC-side socket, then write the command and wait for the
const size_t lenWithMetadata = fullCommandLength - 2; // response.
status = pasynOctetSyncIO->writeRead(
pasynOctetSyncIOipPort(), fullCommand, fullCommandLength, fullResponse,
MAXBUF_, comTimeout, &nbytesOut, &nbytesIn, &eomReason);
// Perform both division and modulo operation at once. // If a communication error occured, print this message to the
div_t lenWithMetadataSep = std::div(lenWithMetadata, 256); msgPrintControlKey comKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
// Now set the actual command length if (status != asynSuccess) {
fullCommand[1] = lenWithMetadataSep.rem; // LSB if (getMsgPrintControl().shouldBePrinted(comKey, true, pasynUserSelf)) {
fullCommand[2] = lenWithMetadataSep.quot; // MSB char printableCommand[MAXBUF_] = {0};
adjustForPrint(printableCommand, fullCommand, MAXBUF_);
adjustForPrint(printableCommand, fullCommand, MAXBUF_); asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
asynPrint( "Controller \"%s\", axis %d => %s, line %d:\nError "
this->pasynUserSelf, ASYN_TRACEIO_DRIVER, "%s while sending command %s to the controller\n",
"Controller \"%s\", axis %d => %s, line %d:\nSending command %s\n", portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableCommand); stringifyAsynStatus(status), printableCommand);
// Send out the command
status =
pasynOctetSyncIO->write(lowLevelPortUser_, fullCommand,
fullCommandLength, comTimeout_, &nbytesOut);
if (status == asynSuccess) {
// Try to read the answer repeatedly
int maxTrials = 2;
for (int i = 0; i < maxTrials; i++) {
status =
pasynOctetSyncIO->read(lowLevelPortUser_, fullResponse, MAXBUF_,
comTimeout_, &nbytesIn, &eomReason);
if (status == asynSuccess) {
status = parseResponse(fullCommand, fullResponse,
drvMessageText, &valueStart, &valueStop,
axisNo, tcpCmd, isRead);
if (status == asynSuccess) {
// Received the correct message
break;
}
} else {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nError "
"%s while reading from the controller\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
break;
}
if (i + 1 == maxTrials && status == asynError) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFailed "
"%d times to get the correct response. Aborting read.\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, maxTrials);
}
} }
} else { } else {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, getMsgPrintControl().resetCount(comKey, pasynUserSelf);
"Controller \"%s\", axis %d => %s, line %d:\nError %s while "
"writing to the controller\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
} }
// MasterMACS needs a bit of time between messages, therefore thr program
// execution is paused after the communication happened.
// usleep(1500);
// Create custom error messages for different failure modes // Create custom error messages for different failure modes
switch (status) { switch (status) {
case asynSuccess: case asynSuccess:
// We did get a response, but does it make sense and is it designated as
// OK from the controller? This is checked here.
status = parseResponse(fullCommand, fullResponse, drvMessageText,
&valueStart, &valueStop, axisNo, tcpCmd, isRead);
if (isRead) { // Read out the important information from the response
if (status == asynSuccess && isRead) {
/* /*
If a property has been read, we need just the part between the If a property has been read, we need just the part between the
"=" (0x3D) and the [ACK] (0x06). Therefore, we remove all "=" (0x3D) and the [ACK] (0x06). Therefore, we remove all
@ -320,70 +230,55 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
response[i] = fullResponse[i + valueStart]; response[i] = fullResponse[i + valueStart];
} }
} }
break; break;
case asynTimeout: case asynTimeout:
snprintf(drvMessageText, sizeof(drvMessageText), snprintf(drvMessageText, sizeof(drvMessageText),
"connection timeout for axis %d", axisNo); "Connection timeout. Please call the support.");
break; break;
case asynDisconnected: case asynDisconnected:
snprintf(drvMessageText, sizeof(drvMessageText), snprintf(drvMessageText, sizeof(drvMessageText),
"axis is not connected"); "Axis is not connected.");
break; break;
case asynDisabled: case asynDisabled:
snprintf(drvMessageText, sizeof(drvMessageText), "axis is disabled"); snprintf(drvMessageText, sizeof(drvMessageText), "Axis is disabled.");
break; break;
case asynError: case asynError:
// Do nothing - error message drvMessageText has already been set. // Do nothing - error message drvMessageText has already been set.
break; break;
default: default:
snprintf(drvMessageText, sizeof(drvMessageText), snprintf(drvMessageText, sizeof(drvMessageText),
"Communication failed (%s)", stringifyAsynStatus(status)); "Communication failed (%s). Please call the support.",
stringifyAsynStatus(status));
break; break;
} }
// Log the overall status (communication successfull or not) // Log the overall status (communication successfull or not)
if (status == asynSuccess) { if (status == asynSuccess) {
adjustForPrint(printableResponse, fullResponse, MAXBUF_); setAxisParamChecked(axis, motorStatusCommsError, false);
asynPrint(
lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
"Controller \"%s\", axis %d => %s, line %d:\nReturn value: %s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableResponse);
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
} else { } else {
// Check if the axis already is in an error communication mode. If /*
// it is not, upstream the error. This is done to avoid "flooding" Since the communication failed, there is the possibility that the
// the user with different error messages if more than one error controller is not connected at all to the network. In that case, we
// ocurred before an error-free communication cannot be sure that the information read out in the init method of the
pl_status = axis is still up-to-date the next time we get a connection. Therefore,
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem); an info flag is set which the axis object can use at the start of its
if (pl_status != asynSuccess) { poll method to try to initialize itself.
return paramLibAccessFailed(pl_status, "motorStatusProblem_", */
axisNo, __PRETTY_FUNCTION__, __LINE__); axis->setNeedInit(true);
}
/*
Check if the axis already is in an error communication mode. If
it is not, upstream the error. This is done to avoid "flooding"
the user with different error messages if more than one error
ocurred before an error-free communication
*/
getAxisParamChecked(axis, motorStatusProblem, &motorStatusProblem);
if (motorStatusProblem == 0) { if (motorStatusProblem == 0) {
pl_status = axis->setStringParam(motorMessageText_, drvMessageText); setAxisParamChecked(axis, motorMessageText, drvMessageText);
if (pl_status != asynSuccess) { setAxisParamChecked(axis, motorStatusProblem, true);
return paramLibAccessFailed(pl_status, "motorMessageText_", setAxisParamChecked(axis, motorStatusCommsError, false);
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem",
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
} }
} }
@ -405,9 +300,22 @@ asynStatus masterMacsController::parseResponse(
bool responseValid = false; bool responseValid = false;
int responseStart = 0; int responseStart = 0;
asynStatus status = asynSuccess;
int prevConnected = 0;
char printableCommand[MAXBUF_] = {0}; char printableCommand[MAXBUF_] = {0};
char printableResponse[MAXBUF_] = {0}; char printableResponse[MAXBUF_] = {0};
msgPrintControlKey parseKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) {
return asynError;
}
// Was the motor previously connected?
getAxisParamChecked(axis, motorConnected, &prevConnected);
// We don't use strlen here since the C string terminator 0x00 // We don't use strlen here since the C string terminator 0x00
// occurs in the middle of the char array. // occurs in the middle of the char array.
for (uint32_t i = 0; i < MAXBUF_; i++) { for (uint32_t i = 0; i < MAXBUF_; i++) {
@ -416,33 +324,83 @@ asynStatus masterMacsController::parseResponse(
} else if (fullResponse[i] == '=') { } else if (fullResponse[i] == '=') {
*valueStart = i + 1; *valueStart = i + 1;
} else if (fullResponse[i] == '\x06') { } else if (fullResponse[i] == '\x06') {
// ACK
*valueStop = i; *valueStop = i;
responseValid = true; responseValid = true;
// Motor wasn't connected before -> Update the paramLib entry and PV
// to show it is now connected.
if (prevConnected == 0) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nAxis connection status has changed to "
"connected.\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(axis, motorConnected, true);
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return status;
}
}
break; break;
} else if (fullResponse[i] == '\x15') { } else if (fullResponse[i] == '\x15') {
// NAK /*
snprintf(drvMessageText, MAXBUF_, "Communication failed."); NAK
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, This indicates that the axis is not connected. This is not an error!
"Controller \"%s\", axis %d => %s, line " */
"%d:\nCommunication failed\n", snprintf(drvMessageText, MAXBUF_, "Axis not connected.");
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
// Motor was connected before -> Update the paramLib entry and PV
// to show it is now disconnected.
if (prevConnected == 1) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nAxis connection status has changed to "
"disconnected.\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(axis, motorConnected, false);
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCould not update parameter library\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return status;
}
}
break; break;
} else if (fullResponse[i] == '\x18') { } else if (fullResponse[i] == '\x18') {
// CAN // CAN
snprintf(drvMessageText, MAXBUF_, snprintf(drvMessageText, MAXBUF_,
"Tried to write with a read-only command. This is a " "Tried to write with a read-only command. This is a "
"bug, please call the support."); "bug, please call the support.");
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nTried to " if (getMsgPrintControl().shouldBePrinted(parseKey, true,
"write with the read-only command %s\n", pasynUserSelf)) {
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, adjustForPrint(printableCommand, fullCommand, MAXBUF_);
printableCommand); asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nTried to "
"write with the read-only command %s.%s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
printableCommand, getMsgPrintControl().getSuffix());
}
responseValid = false; responseValid = false;
break; break;
} }
} }
if (responseValid) { if (responseValid) {
getMsgPrintControl().resetCount(parseKey, pasynUserSelf);
// Check if the response matches the expectations. Each response // Check if the response matches the expectations. Each response
// contains the string "axisNo R tcpCmd" (including the spaces) // contains the string "axisNo R tcpCmd" (including the spaces)
char expectedResponseSubstring[MAXBUF_] = {0}; char expectedResponseSubstring[MAXBUF_] = {0};
@ -457,30 +415,41 @@ asynStatus masterMacsController::parseResponse(
tcpCmd); tcpCmd);
} }
msgPrintControlKey responseMatchKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (strstr(&fullResponse[responseStart], expectedResponseSubstring) == if (strstr(&fullResponse[responseStart], expectedResponseSubstring) ==
NULL) { NULL) {
adjustForPrint(printableCommand, fullCommand, MAXBUF_); adjustForPrint(printableCommand, fullCommand, MAXBUF_);
adjustForPrint(printableResponse, fullResponse, MAXBUF_); adjustForPrint(printableResponse, fullResponse, MAXBUF_);
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, if (getMsgPrintControl().shouldBePrinted(parseKey, true,
"Controller \"%s\", axis %d => %s, line %d:\nMismatched " pasynUserSelf)) {
"response %s to command %s\n", asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, "Controller \"%s\", axis %d => %s, line "
printableResponse, printableCommand); "%d:\nMismatched "
"response %s to command %s.%s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
printableResponse, printableCommand,
getMsgPrintControl().getSuffix());
}
snprintf(drvMessageText, MAXBUF_, snprintf(drvMessageText, MAXBUF_,
"Mismatched response %s to command %s. Please call the " "Mismatched response %s to command %s. Please call the "
"support.", "support.",
printableResponse, printableCommand); printableResponse, printableCommand);
return asynError; return asynError;
} else {
getMsgPrintControl().resetCount(responseMatchKey, pasynUserSelf);
} }
} }
return asynSuccess; return asynSuccess;
} }
asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) { asynStatus masterMacsController::readInt32(asynUser *pasynUser,
epicsInt32 *value) {
// masterMacs can be disabled // masterMacs can be disabled
if (pasynUser->reason == motorCanDisable_) { if (pasynUser->reason == motorCanDisable()) {
*value = 1; *value = 1;
return asynSuccess; return asynSuccess;
} else { } else {
@ -488,6 +457,8 @@ asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
} }
} }
double masterMacsController::comTimeout() { return pMasterMacsC_->comTimeout; }
/***************************************************************************/ /***************************************************************************/
/** The following functions are C-wrappers, and can be called directly from /** The following functions are C-wrappers, and can be called directly from
* iocsh */ * iocsh */

View File

@ -8,9 +8,16 @@
#ifndef masterMacsController_H #ifndef masterMacsController_H
#define masterMacsController_H #define masterMacsController_H
#include "masterMacsAxis.h"
#include "sinqAxis.h" #include "sinqAxis.h"
#include "sinqController.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 masterMacsAxis;
struct masterMacsControllerImpl;
class masterMacsController : public sinqController { class masterMacsController : public sinqController {
@ -31,6 +38,15 @@ class masterMacsController : public sinqController {
int numAxes, double movingPollPeriod, int numAxes, double movingPollPeriod,
double idlePollPeriod, double comTimeout); double idlePollPeriod, double comTimeout);
/**
* @brief Overloaded version of the sinqController version
*
* @param pasynUser
* @param value
* @return asynStatus
*/
asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
/** /**
* @brief Get the axis object * @brief Get the axis object
* *
@ -38,7 +54,7 @@ class masterMacsController : public sinqController {
* @return masterMacsAxis* If no axis could be found, this is a * @return masterMacsAxis* If no axis could be found, this is a
* nullptr * nullptr
*/ */
masterMacsAxis *getAxis(asynUser *pasynUser); masterMacsAxis *getMasterMacsAxis(asynUser *pasynUser);
/** /**
* @brief Get the axis object * @brief Get the axis object
@ -47,10 +63,7 @@ class masterMacsController : public sinqController {
* @return masterMacsAxis* If no axis could be found, this is a * @return masterMacsAxis* If no axis could be found, this is a
* nullptr * nullptr
*/ */
masterMacsAxis *getAxis(int axisNo); masterMacsAxis *getMasterMacsAxis(int axisNo);
protected:
asynUser *lowLevelPortUser_;
/** /**
* @brief Send a command to the hardware (S mode) * @brief Send a command to the hardware (S mode)
@ -60,7 +73,8 @@ class masterMacsController : public sinqController {
* @param payload Value send to MasterMACS. * @param payload Value send to MasterMACS.
* @return asynStatus * @return asynStatus
*/ */
asynStatus write(int axisNo, int tcpCmd, const char *payload); asynStatus write(int axisNo, int tcpCmd, const char *payload,
double comTimeout = -1.0);
/** /**
* @brief Send a command to the hardware and receive a response (R mode) * @brief Send a command to the hardware and receive a response (R mode)
@ -71,7 +85,8 @@ class masterMacsController : public sinqController {
* expected to have the size MAXBUF_. * expected to have the size MAXBUF_.
* @return asynStatus * @return asynStatus
*/ */
asynStatus read(int axisNo, int tcpCmd, char *response); asynStatus read(int axisNo, int tcpCmd, char *response,
double comTimeout = -1.0);
/** /**
* @brief Send a command to the hardware (R or S mode) and receive a * @brief Send a command to the hardware (R or S mode) and receive a
@ -88,7 +103,7 @@ class masterMacsController : public sinqController {
* @return asynStatus * @return asynStatus
*/ */
asynStatus writeRead(int axisNo, int tcpCmd, const char *payload, asynStatus writeRead(int axisNo, int tcpCmd, const char *payload,
char *response); char *response, double comTimeout = -1.0);
/** /**
* @brief Parse "fullResponse" received upon sending "fullCommand". * @brief Parse "fullResponse" received upon sending "fullCommand".
@ -114,35 +129,20 @@ class masterMacsController : public sinqController {
int *valueStop, int axisNo, int tcpCmd, int *valueStop, int axisNo, int tcpCmd,
bool isRead); bool isRead);
/**
* @brief Save cast of the given asynAxis pointer to a masterMacsAxis
* pointer. If the cast fails, this function returns a nullptr.
*
* @param asynAxis
* @return masterMacsAxis*
*/
masterMacsAxis *castToAxis(asynMotorAxis *asynAxis);
private:
// Set the maximum buffer size. This is an empirical value which must be // Set the maximum buffer size. This is an empirical value which must be
// large enough to avoid overflows for all commands to the device / // large enough to avoid overflows for all commands to the device /
// responses from it. // responses from it.
static const uint32_t MAXBUF_ = 200; static const uint32_t MAXBUF_ = 200;
/* /**
Stores the constructor input comTimeout * @brief Get the communication timeout used in the writeRead command
*/ *
double comTimeout_; * @return double Timeout in seconds
*/
double comTimeout();
// Indices of additional PVs private:
#define FIRST_masterMacs_PARAM rereadEncoderPosition_ std::unique_ptr<masterMacsControllerImpl> pMasterMacsC_;
int rereadEncoderPosition_;
int readConfig_;
#define LAST_masterMacs_PARAM readConfig_
friend class masterMacsAxis;
}; };
#define NUM_masterMacs_DRIVER_PARAMS \
(&LAST_masterMacs_PARAM - &FIRST_masterMacs_PARAM + 1)
#endif /* masterMacsController_H */ #endif /* masterMacsController_H */

View File

@ -2,9 +2,14 @@
Code shared by "decodeError.py" and "decodeStatus.py" Code shared by "decodeError.py" and "decodeStatus.py"
""" """
import struct
def decode(value: int, interpretation): def decode(value: int, interpretation):
bit_list = [int(char) for char in bin(value)[2:]] # Pack the input as a short and unpack it as an unsigned short
value_uint16 = format(value, '016b') # Format as 16-bit unsigned integer
bit_list = [int(char) for char in value_uint16]
bit_list.reverse() bit_list.reverse()
interpreted = [] interpreted = []

View File

@ -32,109 +32,6 @@ interpretation = [
("Not specified", "Not specified"), # Bit 15 ("Not specified", "Not specified"), # Bit 15
] ]
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]:
(bit_list, interpreted) = decode(history[ptr])
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()
if __name__ == "__main__": if __name__ == "__main__":
from sys import argv from sys import argv

171
utils/writeRead.py Normal file
View File

@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""
This script allows direct interaction with a MasterMACS-Controller over an ethernet connection.
To read the manual, simply run this script without any arguments.
Stefan Mathis, April 2025
"""
import struct
import socket
import curses
def packMasterMacsCommand(command):
# 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
expectAck = False
continue
if expectAck and bint == 6: # ACK
return bytes(msg)
else:
if bint == 13 and not expectAck: # CR
return bytes(msg)
else:
msg.append(bint)
if __name__ == "__main__":
from sys import argv
try:
addr = argv[1].split(':')
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((addr[0],int(addr[1])))
if len(argv) == 3:
buf = packMasterMacsCommand(argv[2])
s.send(buf)
reply = readMasterMacsReply(s)
print(reply.decode('utf-8') + '\n')
else:
try:
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]:
buf = packMasterMacsCommand(history[ptr])
s.send(buf)
reply = readMasterMacsReply(s)
stdscr.addstr("\n" + reply.decode('utf-8')[0:-1])
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()
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.
""")