Compare commits

...

16 Commits
1.4.0 ... 1.5.7

Author SHA1 Message Date
e234d05815 Fixed segfault if an axis is misconfigured (i.e. is not a sinqAxis
All checks were successful
Test And Build / Lint (push) Successful in 6s
Test And Build / Build (push) Successful in 5s
2025-12-23 11:42:46 +01:00
1d1e562490 Merge branch 'main' of gitea.psi.ch:lin-epics-modules/sinqMotor 2025-12-23 11:32:23 +01:00
e2b4c330a7 Revised and reformatted README 2025-12-23 11:32:20 +01:00
a6ca695513 Update db/sinqMotor.db
All checks were successful
Test And Build / Lint (push) Successful in 11s
Test And Build / Build (push) Successful in 9s
Fixed wrong docs
2025-12-04 14:06:45 +01:00
59a5ba452f Fixed ordering
All checks were successful
Test And Build / Build (push) Successful in 6s
Test And Build / Lint (push) Successful in 5s
2025-09-17 12:37:55 +02:00
6dc2b131f7 Exempt EPICS libraries from -Weffc++
Some checks failed
Test And Build / Build (push) Failing after 6s
Test And Build / Lint (push) Successful in 26s
2025-09-17 12:33:58 +02:00
902b18d038 Excempt EPICS libraries from -Weffc++
All checks were successful
Test And Build / Lint (push) Successful in 6s
Test And Build / Build (push) Successful in 7s
2025-09-17 12:18:06 +02:00
0e10bcf69d Fixed some more warnings
All checks were successful
Test And Build / Lint (push) Successful in 5s
Test And Build / Build (push) Successful in 6s
2025-09-17 11:28:12 +02:00
cb4adb068c Fixed some warnings
All checks were successful
Test And Build / Lint (push) Successful in 6s
Test And Build / Build (push) Successful in 5s
2025-09-17 11:24:15 +02:00
d7c9d009ee Better solution for suppressing unused variable warning
All checks were successful
Test And Build / Lint (push) Successful in 5s
Test And Build / Build (push) Successful in 5s
2025-09-17 11:08:24 +02:00
3ab40a8bf5 Fixed compiler warnings
Some checks failed
Test And Build / Lint (push) Failing after 2s
Test And Build / Build (push) Successful in 7s
2025-09-17 10:52:56 +02:00
0478854007 Added compiler flags to improve code quality 2025-09-17 10:25:50 +02:00
9a32532c22 Expanded error messageto give users the ability to help themselves
All checks were successful
Test And Build / Lint (push) Successful in 5s
Test And Build / Build (push) Successful in 6s
2025-09-17 10:24:07 +02:00
cff64f5ecf Added new feature to set deadband
All checks were successful
Test And Build / Lint (push) Successful in 6s
Test And Build / Build (push) Successful in 12s
The field SPDB can now be populated via either the substitutions file or
from inside the driver (using the motorPositionDeadband paramLib entry).
2025-09-09 16:50:26 +02:00
7a0de4e9d9 Perform callParamCallbacks even if movement watchdog timed out
All checks were successful
Test And Build / Lint (push) Successful in 6s
Test And Build / Build (push) Successful in 5s
2025-08-14 17:15:21 +02:00
566728c57c Fixed error in README.md
All checks were successful
Test And Build / Lint (push) Successful in 5s
Test And Build / Build (push) Successful in 5s
2025-08-12 09:15:55 +02:00
9 changed files with 449 additions and 213 deletions

View File

@@ -26,6 +26,7 @@ TEMPLATES += db/sinqMotor.db
# This file registers the motor-specific functions in the IOC shell.
DBDS += src/sinqMotor.dbd
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result # -Werror
USR_CFLAGS += -Wall -Wextra -Wunused-result # -Werror
USR_CXXFLAGS += -Wall -Wextra -Wunused-result
# MISCS would be the place to keep the stream device template files

291
README.md
View File

@@ -2,67 +2,47 @@
## Overview
This library offers base classes for EPICS motor drivers (`sinqAxis` and `sinqController`) of PSI SINQ. These classes are extensions of the classes `asynMotorAxis` and `asynMotorController` from the `asynMotor` framework (https://github.com/epics-modules/motor/tree/master/motorApp/MotorSrc) and bundle some common functionality.
This library offers base classes for EPICS motor drivers (`sinqAxis` and
`sinqController`) of PSI SINQ. These classes are extensions of the classes
`asynMotorAxis` and `asynMotorController` from the `asynMotor` framework
(https://github.com/epics-modules/motor/tree/master/motorApp/MotorSrc) and
bundle some common functionality.
## User guide
### Architecture of EPICS motor drivers at SINQ
The asyn-framework offers two base classes `asynMotorAxis` and `asynMotorController`. At SINQ, we extend those classes by two children `sinqAxis` and `sinqController` which are not complete drivers on their own, but serve as a framework extension for writing drivers. The concrete drivers are then created as separated libraries, an example is the TurboPMAC-driver: https://git.psi.ch/sinq-epics-modules/turboPmac.
The asyn-framework offers two base classes `asynMotorAxis` and
`asynMotorController`. At SINQ, we extend those classes by two children
`sinqAxis` and `sinqController` which are not complete drivers on their own, but
serve as a framework extension for writing drivers. The concrete drivers are
then created as separated libraries, an example is the TurboPMAC-driver:
https://git.psi.ch/sinq-epics-modules/turboPmac.
The full inheritance chain for two different motor drivers "a" and "b" looks like this:
The full inheritance chain for two different motor drivers "a" and "b" looks
like this:
`asynController -> sinqController -> aController`
`asynAxis -> sinqAxis -> aAxis`
`asynController -> sinqController -> bController`
`asynAxis -> sinqAxis -> bAxis`
Those inheritance chains are created at runtime by loading shared libraries. These libraries must be compatible to each other (see next section).
`asynMotorAxis` and `asynMotorController` are provided by the shared `asynMotor`
library. The drivers "a" and "b" should then include the "sinqMotor" repository
as submodules and directly compile against the source code of `sinqMotor`.
### Versioning
In order to make sure the shared libraries are compatible to each other, we use the "require" framework extension for EPICS (https://github.com/paulscherrerinstitute/require). If a shared library has another library as a dependency, it is checked whether the latter is already loaded. If yes, the loaded version is considered compatible if:
1) no specific version was required by the former library
2) the already loaded version matches the required version exactly
3) major and minor numbers are the same and already loaded patch number is equal to the required one or higher
4) major numbers are the same and already loaded minor number is higher than the required one
5) the already loaded version is a test version and the required version is not a test version
These rules are in complicance with the SemVer standard (https://semver.org/lang/de/)
If the dependency hasn't been loaded yet, it is loaded now. In case no specific version is required, the latest numbered version is used.
Because these rules are checked sequentially for each required dependency and no unloading is performed, it is important to consider the order of required libraries. Consider the following example:
```
require "libDriverA" # sinqMotor 1.2 is specified as a dependency
require "libDriverB" # sinqMotor 1.0 is specified as a dependency
```
`require` first checks the dependencies of `libDriverA` and sees that `sinqMotor 1.2` is required. It therefore load `sinqMotor 1.2` and then `libDriverA`. Now the next `require` starts analyzing the dependencies of `libDriverB` and sees that `sinqMotor 1.0` is required. Since `sinqMotor 1.2` is already loaded, rule 4) is applied and `libDriverB` is assumed to be compatible with `sinqMotor 1.2` as well (which it should be according to SemVer).
When the order is inverted, the following happens:
```
require "libDriverB" # sinqMotor 1.0 is specified as a dependency
require "libDriverA" # sinqMotor 1.2 is specified as a dependency
```
`require` first checks the dependencies of `libDriverB` and sees that `sinqMotor 1.0` is required. It therefore load `sinqMotor 1.0` and then `libDriverB`. Now the next `require` starts analyzing the dependencies of `libDriverA` and sees that `sinqMotor 1.2` is required. Since `sinqMotor 1.0` is already loaded, `require` cannot load `sinqMotor 1.2`. Therefore the IOC startup is aborted with an error message.
In order to make the setup script more robust, it is therefore recommended to explicitly add a dependency version which is compatible to all required libraries:
```
require "sinqMotor", "1.2"
require "libDriverB" # sinqMotor 1.0 is specified as a dependency
require "libDriverA" # sinqMotor 1.2 is specified as a dependency
```
The IOC startup now succeeds because we made sure the higher version is loaded first.
Please see the README.md of https://github.com/paulscherrerinstitute/require for more details.
To find out which version of sinqMotor is needed by a driver, refer to its Makefile (line `sinqMotor_VERSION=x.x.x`, where x.x.x is the minimum required version).
Versioning of `sinqMotor` and of derived drivers follows the SemVer standard
(https://semver.org/lang/de/). This standard is also used by the "require"
extension for EPICS (https://github.com/paulscherrerinstitute/require) used at
SINQ.
### IOC startup script
An EPICS IOC for motor control at SINQ is started by executing a script with the IOC shell. In its simplest form, an IOC for two controllers run by "exampleDriver" is a file looking like this:
An EPICS IOC for motor control at SINQ is started by executing a script with the
IOC shell. In its simplest form, an IOC for two controllers run by
"exampleDriver" is a file looking like this:
```
#!/usr/local/bin/iocsh
@@ -79,17 +59,21 @@ epicsEnvSet("INSTR","SQ:SINQTEST:")
iocInit()
```
The first line is a so-called shebang which instructs Linux to execute the file with the executable located at the given path - the IOC shell in this case. The files `exampleDriver1.cmd` or `exampleDriver2` then look like this:
The first line is a so-called shebang which instructs Linux to execute the file
with the executable located at the given path - the IOC shell in this case. The
files `exampleDriver1.cmd` or `exampleDriver2` then look like this:
```
# Define the name of the controller and the corresponding port
epicsEnvSet("DRIVER_PORT","exampleDriver1")
epicsEnvSet("IP_PORT","p$(DRIVER_PORT)")
# 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
# 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("$(IP_PORT)","172.28.101.24:1025")
# Create the controller object with the defined name and connect it to the socket via the port name.
# 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
# 0.05: Busy poll period in seconds
@@ -97,7 +81,8 @@ drvAsynIPPortConfigure("$(IP_PORT)","172.28.101.24:1025")
# 1: Socket communication timeout in seconds
actualDriverController("$(DRIVER_PORT)", "$(IP_PORT)", 8, 0.05, 1, 1);
# Define some axes for the specified motor controller at the given slot (1, 2 and 5). No slot may be used twice!
# Define some axes for the specified motor controller at the given slot (1, 2
# and 5). No slot may be used twice!
actualDriverAxis("$(DRIVER_PORT)",1);
actualDriverAxis("$(DRIVER_PORT)",2);
actualDriverAxis("$(DRIVER_PORT)",5);
@@ -110,7 +95,8 @@ setMaxSubsequentTimeouts("$(DRIVER_PORT)", 20);
# the previously stated busy poll period.
setForcedFastPolls("$(DRIVER_PORT)", 10);
# Configure the timeout frequency watchdog: A maximum of 10 timeouts are allowed in 300 seconds before an alarm message is sent.
# Configure the timeout frequency watchdog: A maximum of 10 timeouts are allowed
# in 300 seconds before an alarm message is sent.
setThresholdComTimeout("$(DRIVER_PORT)", 300, 10);
# Parametrize the EPICS record database with the substitution file named after the motor controller.
@@ -123,8 +109,10 @@ dbLoadRecords("$(exampleDriver_DB)/asynRecord.db","P=$(INSTR)$(DRIVER_PORT),PORT
### Substitution file
The substitution file is a table containing axis-specific information which is used to create the axis-specific PVs.
To work with sinqMotor, `exampleDriver1.substitutions` needs to look like this (the order of columns does not matter):
The substitution file is a table containing axis-specific information which is
used to create the axis-specific PVs. To work with sinqMotor,
`exampleDriver1.substitutions` needs to look like this (the order of columns
does not matter):
```
file "$(SINQDBPATH)"
{
@@ -140,23 +128,44 @@ The variable `SINQDBPATH` has been set in "mcu1.cmd" before calling `dbLoadTempl
#### Mandatory parameters
- `AXIS`: Index of the axis, corresponds to the physical connection of the axis to the MCU.
- `M`: The full PV name is created by concatenating the variables INSTR, DRIVER_PORT and M. For example, the PV of the first axis would be "SQ:SINQTEST:mcu1:lin1".
- `EGU`: Engineering units. For a linear motor, this is mm, for a rotaty motor, this is degree.
- `AXIS`: Index of the axis, corresponds to the physical connection of the axis
to the MCU.
- `M`: The full PV name is created by concatenating the variables INSTR,
DRIVER_PORT and M. For example, the PV of the first axis would be
"SQ:SINQTEST:mcu1:lin1".
- `EGU`: Engineering units. For a linear motor, this is mm, for a rotaty motor,
this is degree.
- `DIR`: If set to "Neg", the axis direction is inverted.
- `MRES`: This is a scaling factor determining the resolution of the position readback value. For example, 0.001 means a precision of 1 um. A detailed description can be found in section [Motor record resolution MRES](#motor-record-resolution-mres).
- `MRES`: This is a scaling factor determining the resolution of the position
readback value. For example, 0.001 means a precision of 1 um. A detailed
description can be found in section [Motor record resolution MRES](#motor-record-resolution-mres).
#### Optional parameters
The default values for those parameters are given for the individual records in db/sinqMotor.db
- `DESC`: Description of the motor. This field is just for documentation and is not needed for operating a motor. Defaults to the motor name.
- `MSGTEXTSIZE`: Buffer size for the motor message record in characters. Defaults to 200 characters
- `ENABLEMOVWATCHDOG`: Sets `setWatchdogEnabled` during IOC startup to the given value. Defaults to 0.
The default values for those parameters are given for the individual records in
`db/sinqMotor.db`
- `DESC`: Description of the motor. This field is just for documentation and is
not needed for operating a motor. Defaults to the motor name.
- `MSGTEXTSIZE`: Buffer size for the motor message record in characters.
Defaults to 200 characters
- `ENABLEMOVWATCHDOG`: Sets `setWatchdogEnabled` during IOC startup to the given
value. Defaults to 0.
- `LIMITSOFFSET`: If the motor limits are read out from the controller, they can
be further reduced by this offset in order to avoid errors due to slight overshoot
on the motor controller. For example, if this value is 1.0 and the read-out limits
are [-10.0 10.0], the EPICS limits are set to [-9.0 9.0]. This parameter uses engineering units (EGU). Defaults to 0.0.
- `CANSETSPEED`: If set to 1, the motor speed can be modified by the user. Defaults to 0.
- `ADAPTPOLL`: If set to any value other than 0, adaptive polling is enabled for this particular axis. Adaptive polling is designed to reduce the communication load in case some axis is moving. By default, if at least one axis is moving, all axes are polled using the busy / moving poll period (see [IOC startup script](#ioc-startup-script)). Adaptive polling modifies this behaviour so that the affected axis is only polled with the busy / moving poll period if it itself is moving. This setting is ignored for "forced fast polls" (when the poller is woken up, e.g. after an axis received a move command). Defaults to 1.
be further reduced by this offset in order to avoid errors due to slight
overshoot on the motor controller. For example, if this value is 1.0 and the
read-out limits are [-10.0 10.0], the EPICS limits are set to [-9.0 9.0]. This
parameter uses engineering units (EGU). Defaults to 0.0.
- `CANSETSPEED`: If set to 1, the motor speed can be modified by the user.
Defaults to 0.
- `ADAPTPOLL`: If set to any value other than 0, adaptive polling is enabled for
this particular axis. Adaptive polling is designed to reduce the communication
load in case some axis is moving. By default, if at least one axis is moving,
all axes are polled using the busy / moving poll period (see
[IOC startup script](#ioc-startup-script)). Adaptive polling modifies this
behaviour so that the affected axis is only polled with the busy / moving poll
period if it itself is moving. This setting is ignored for "forced fast polls"
(when the poller is woken up, e.g. after an axis received a move command).
Defaults to 1.
### Motor record resolution MRES
@@ -200,30 +209,46 @@ transferred to (motor_record_pv_name).MRES or to
### Additional records
`sinqMotor` provides a variety of additional records. See `db/sinqMotor.db` for the complete list and the documentation.
`sinqMotor` provides a variety of additional records. See `db/sinqMotor.db` for
the complete list and the documentation.
## Developer guide
### File structure
sinqMotor offers a variety of additional methods for children classes to standardize certain patterns (e.g. writing messages to the IOC shell and the motor message PV). For a detailed description, please see the respective function documentation in the .h-files. All of these functions can be overwritten manually if e.g. a completely different implementation of `poll` is required. Some functions are marked as virtual, because they are called from other functions of sinqMotor and therefore need runtime polymorphism. Functions without that marker are not called anywhere in sinqMotor (except for `forcedPoll`, which is called in `poll`).
sinqMotor offers a variety of additional methods for children classes to
standardize certain patterns (e.g. writing messages to the IOC shell and the
motor message PV). For a detailed description, please see the respective
function documentation in the .h-files. All of these functions can be
overwritten manually if e.g. a completely different implementation of `poll` is
required. Some functions are marked as virtual, because they are called from
other functions of sinqMotor and therefore need runtime polymorphism. Functions
without that marker are not called anywhere in sinqMotor (except for
`forcedPoll`, which is called in `poll`).
Adding new virtual methods breaks the ABI and therefore warrants a new major version number!
Adding new virtual methods breaks the ABI and therefore warrants a new major
version number!
#### sinqController.h
- `couldNotParseResponse`: Write a standardized message if parsing a device response failed.
- `couldNotParseResponse`: Write a standardized message if parsing a deviceresponse failed.
- `paramLibAccessFailed`: Write a standardized message if accessing the parameter library failed.
- `stringifyAsynStatus`: Convert the enum `asynStatus` into a human-readable string.
- `checkComTimeoutWatchdog`: Calculates the timeout frequency (number of timeouts in a given time) and informs the user if a specified limit has been exceeded.
- `setThresholdComTimeout`: Set the maximum number of timeouts and the time window size for the timeout frequency limit. This function is also available in the IOC shell.
- `checkMaxSubsequentTimeouts`: Check if the number of subsequent timeouts exceeds a specified limit.
- `setMaxSubsequentTimeouts`: Set the limit for the number of subsequent timeouts before the user is informed. This function is also available in the IOC shell.
- `setForcedFastPolls`: Set the number of forced fast polls which are performed after the poller has been "woken up" ( = after `wakePoller()` is called). This function is also available in the IOC shell.
- `checkComTimeoutWatchdog`: Calculates the timeout frequency (number of
timeouts in a given time) and informs the user if a specified limit has been exceeded.
- `setThresholdComTimeout`: Set the maximum number of timeouts and the time window
size for the timeout frequency limit. This function is also available in the IOC shell.
- `checkMaxSubsequentTimeouts`: Check if the number of subsequent timeouts
exceeds a specified limit.
- `setMaxSubsequentTimeouts`: Set the limit for the number of subsequent timeouts
before the user is informed. This function is also available in the IOC shell.
- `setForcedFastPolls`: Set the number of forced fast polls which are performed
after the poller has been "woken up" ( = after `wakePoller()` is called). This
function is also available in the IOC shell.
#### sinqAxis.h
- `enable`: This function is called if the `$(INSTR)$(M):Enable` PV from db/sinqMotor.db is set.
- `enable`: This function is called if the `$(INSTR)$(M):Enable` PV from `db/sinqMotor.db` is set.
This is an empty function which should be overwritten by concrete driver implementations.
- `reset`: This function is called when the `$(INSTR)$(M):Reset` PV from db/sinqMotor.db is set.
- `reset`: This function is called when the `$(INSTR)$(M):Reset` PV from `db/sinqMotor.db` is set.
It calls `doReset` and performs some fast polls after `doReset` returns.
- `doReset`: This is an empty function which should be overwritten by concrete driver implementations.
- `move`: This function sets the absolute target position in the parameter library and then calls `doMove`.
@@ -231,62 +256,98 @@ It calls `doReset` and performs some fast polls after `doReset` returns.
- `home`: This function sets the internal status flags for the homing process and then calls doHome.
- `doHome`: This is an empty function which should be overwritten by concrete driver implementations.
- `poll`: This is a wrapper around `forcedPoll` which does the following checks before calling `forcedPoll`:
- Are there any outstanding fast polls (method `outstandingForcedFastPolls` of the controller returns a value greater zero)?
- Are there any outstanding fast polls (method `outstandingForcedFastPolls` of
the controller returns a value greater zero)?
- Was the axis moving last time its status was polled?
- Is adaptive polling disabled?
- Did an idle period pass since the last poll?
If all of these conditions are false, no poll is performed. Otherwise, the `forcedPoll` method is called.
This method should not be called in the driver code itself if a poll is needed - use `forcedPoll` instead!
If all of these conditions are false, no poll is performed. Otherwise, the
`forcedPoll` method is called. This method should not be called in the driver
code itself if a poll is needed - use `forcedPoll` instead!
- `forcedPoll`: This is a wrapper around `doPoll` which performs some bookkeeping tasks before and after calling `doPoll`:
- `forcedPoll`: This is a wrapper around `doPoll` which performs some
bookkeeping tasks before and after calling `doPoll`:
Before calling `doPoll`:
- Check if the paramLib already contains an old error message. If so, put it into a temporary bufffer
- Check if the paramLib already contains an old error message. If so, put it
into a temporary bufffer
After calling `doPoll`:
- Call `checkMovTimeoutWatchdog`. If the movement timed out, create an error message for the user
- Call `checkMovTimeoutWatchdog`. If the movement timed out, create an error
message for the user
- Update the readback-value for the axis enablement.
- If `doPoll` returns anything other than `asynSuccess` or if an old error message is waiting in the temporary buffer, set `motorStatusProblem` to true, otherwise to false. If an old error message is waiting in the temporary buffer, but `doPoll` returned `asynSuccess`, overwrite the paramLib entry for `motorMessageText` with the old error message.
- If `doPoll` returns anything other than `asynSuccess` or if an old error
message is waiting in the temporary buffer, set `motorStatusProblem` to true,
otherwise to false. If an old error message is waiting in the temporary
buffer, but `doPoll` returned `asynSuccess`, overwrite the paramLib entry for
`motorMessageText` with the old error message.
- Run `callParamCallbacks`
- Reset `motorMessageText` AFTER updating the PVs. This makes sure that the error message is shown for at least one poll cycle.
- Reset `motorMessageText` AFTER updating the PVs. This makes sure that the
error message is shown for at least one poll cycle.
- Return the status of `doPoll`
- `motorPosition`: Returns the parameter library value of the motor position, accounted for the motor record resolution (see section "Motor record resolution MRES")
- `setMotorPosition`: Writes the given value into the parameter library, accounted for the motor record resolution (see section "Motor record resolution MRES")
- `setVeloFields`: Populates the motor record fields VELO (actual velocity), VBAS (minimum allowed velocity) and VMAX (maximum allowed velocity) from the driver.
- `motorPosition`: Returns the parameter library value of the motor position,
accounted for the motor record resolution (see section "Motor record resolution MRES")
- `setMotorPosition`: Writes the given value into the parameter library,
accounted for the motor record resolution (see section "Motor record resolution MRES")
- `setVeloFields`: Populates the motor record fields VELO (actual velocity),
VBAS (minimum allowed velocity) and VMAX (maximum allowed velocity) from the driver.
- `setAcclField`: Populates the motor record field ACCL from the driver.
- `startMovTimeoutWatchdog`: Starts a watchdog for the movement time. This watchdog compares the actual time spent in a movement operation with an expected time, which is calculated based on the distance of the current and the target position.
- `startMovTimeoutWatchdog`: Starts a watchdog for the movement time. This
watchdog compares the actual time spent in a movement operation with an expected
time, which is calculated based on the distance of the current and the target position.
- `checkMovTimeoutWatchdog`: Check if the watchdog timed out.
- `setWatchdogEnabled`: Enables / disables the watchdog. This function is also available in the IOC shell.
- `setOffsetMovTimeout`: Set a linear offset for the expected movement time. This function is also available in the IOC shell.
- `setScaleMovTimeout`: Set a scaling factor for the expected movement time. This function is also available in the IOC shell.
- `setWatchdogEnabled`: Enables / disables the watchdog. This function is also
available in the IOC shell.
- `setOffsetMovTimeout`: Set a constant offset for the expected movement time.
This function is also available in the IOC shell.
- `setScaleMovTimeout`: Set a scaling factor for the expected movement time.
This function is also available in the IOC shell.
#### msgPrintControl.h
In addition to the two extension classes this library also includes a mechanism which prevents excessive repetitions of the same error message to the IOC shell via the classes `msgPrintControl` and `msgPrintControlKey`. A detailed description of the mechanism can be found in the docstring of `msgPrintControl`. The implementation of the `poll` function of `sinqAxis` also contains an example how to use it. Using this feature in derived drivers is entirely optional.
In addition to the two extension classes this library also includes a mechanism
which prevents excessive repetitions of the same error message to the IOC shell
via the classes `msgPrintControl` and `msgPrintControlKey`. A detailed
description of the mechanism can be found in the docstring of `msgPrintControl`.
The implementation of the `poll` function of `sinqAxis` also contains an example
how to use it. Using this feature in derived drivers is entirely optional.
#### macros.h
Contains macros used in `sinqMotor` and derived drivers:
- **HIDDEN**
By default, the symbols of classes and functions are hidden to avoid symbol clashes when loading
multiple shared libraries which use `sinqMotor`. In order to compile this library with exported
symbols, specifiy `-DHIDDEN= ` as a compiler flag (if using the given Makefile, this
needs to be added to the `USR_CFLAGS`).
By default, the symbols of classes and functions are hidden to avoid symbol
clashes when loadingmultiple shared libraries which use `sinqMotor`. In order
to compile this library with exported symbols, specifiy `-DHIDDEN= ` as a
compiler flag (if using the given Makefile, this needs to be added to the
`USR_CFLAGS`).
Derived libraries can use the same mechanism via the macro `HIDDEN` (defined in `macros.h`):
Derived libraries can use the same mechanism via the macro `HIDDEN` (defined
in `macros.h`):
```
class HIDDEN turboPmacController : public sinqController
```
### Versioning
The versioning is done via git tags. Git tags are recognized by the PSI build system: If you tag a version as 1.0, it will be built into the directory /ioc/modules/sinqMotor/1.0. The tag is directly coupled to a commit so that it is always clear which source code was used to build which binary.
The versioning is done via git tags. Git tags are recognized by the PSI build
system: If you tag a version as 1.0, it will be built into the directory
`/ioc/modules/sinqMotor/1.0`. The tag is directly coupled to a commit so that it
is always clear which source code was used to build which binary.
All existing tags can be listed with `git tag` in the sinqMotor directory. Detailed information (author, data, commit number, commit message) regarding a specific tag can be shown with `git show x.x.x`, where `x.x.x` is the name of your version. To create a new tag, use `git tag x.x.x`. If the tag `x.x.x` is already used by another commit, git will show a corresponding error.
All existing tags can be listed with `git tag` in the sinqMotor directory.
Detailed information (author, data, commit number, commit message) regarding a
specific tag can be shown with `git show x.x.x`, where `x.x.x` is the name of
your version. To create a new tag, use `git tag x.x.x`. If the tag `x.x.x` is
already used by another commit, git will show a corresponding error.
### Dependencies
This library is based on the PSI version of the EPICS motor record, which can be found here: `https://git.psi.ch/epics_driver_modules/motorBase`. We use a branch with a bugfix which is currently not merged into master due to resistance of the PSI userbase: `https://git.psi.ch/epics_driver_modules/motorBase/-/tree/pick_fix-lockup-VAL-HOMF-VAL`. This library can be build with the following steps, assuming GCC and make are available:
This library is based on the PSI version of the EPICS motor record, which can be
found here: `https://git.psi.ch/epics_driver_modules/motorBase`. We use a
branch with a bugfix which is currently not merged into master due to resistance
of the PSI userbase: `https://git.psi.ch/epics_driver_modules/motorBase/-/tree/pick_fix-lockup-VAL-HOMF-VAL`.
This library can be build with the following steps, assuming GCC and make are available:
- `git clone https://git.psi.ch/epics_driver_modules/motorBase/-/tree/pick_fix-lockup-VAL-HOMF-VAL`
- `cd motorBase`
- `git tag 7.2.2`. The latest version on master is currently 7.2.1, hence we increment the bugfix version counter by one
@@ -294,29 +355,49 @@ This library is based on the PSI version of the EPICS motor record, which can be
### Usage as dynamic dependency
The makefile in the top directory includes all necessary steps for compiling a shared library of sinqMotor together with the header files into `/ioc/modules` (using the PSI EPICS build system). Therefore it is sufficient to clone this repository to a suitable location (`git clone https://git.psi.ch/sinq-epics-modules/sinqmotor/-/tree/main`). Afterwards, switch to the directory (`cd sinqmotor`) and run `make install`.
The makefile in the top directory includes all necessary steps for compiling a
shared library of sinqMotor together with the header files into `/ioc/modules`
(using the PSI EPICS build system). Therefore it is sufficient to clone this
repository to a suitable location
(`git clone https://git.psi.ch/sinq-epics-modules/sinqmotor/-/tree/main`).
Afterwards, switch to the directory (`cd sinqmotor`) and run `make install`.
To use the library when writing a concrete motor driver, include it in the makefile of your application / library the same way as other libraries such as e.g. `asynMotor` by adding `REQUIRED+=sinqMotor` to your Makefile. The version can be specified with `sinqMotor_VERSION=x.x.x.`
To use the library when writing a concrete motor driver, include it in the
makefile of your application / library the same way as other libraries such as
e.g. `asynMotor` by adding `REQUIRED+=sinqMotor` to your Makefile. The version
can be specified with `sinqMotor_VERSION=x.x.x.`
### Usage as static dependency
This repository is included as a git submodule in the driver repositories depending upon sinqMotor. When installing via a Makefile (`make install`) using the PSI build system, the following git command is executed within `/ioc/tools/driver.makefile`:
This repository is included as a git submodule in the driver repositories
depending upon sinqMotor. When installing via a Makefile (`make install`) using
the PSI build system, the following git command is executed within
`/ioc/tools/driver.makefile`:
`git submodule update --init --recursive`
This forces each submodule to be checked out at the latest commit hash stored in the remote repository. However, this is usually unwanted behaviour, since the higher-level drivers are usually designed to be compiled against a specific version of sinqMotor. In order to set the submodule to a specific version, the following steps need to be done BEFORE calling `make install`:
This forces each submodule to be checked out at the latest commit hash stored in
the remote repository. However, this is usually unwanted behaviour, since the
higher-level drivers are usually designed to be compiled against a specific
version of sinqMotor. In order to set the submodule to a specific version, the
following steps need to be done BEFORE calling `make install`:
- `cd sinqMotor`
- `git checkout 1.0`
- `cd ..`
Then, the fixation of the version to 1.0 needs to be committed in the parent repository:
Then, the fixation of the version to 1.0 needs to be committed in the parent
repository:
- `git commit -m "Update sinqMotor to 1.0"`
After this commit, running `make install` will use the correct driver version for compilation.
After this commit, running `make install` will use the correct driver version
for compilation.
If your driver uses another driver as a static dependency via git submodule which in turn includes a sinqMotor submodule, it is not necessary to specify the version of sinqMotor. Instead, specify the desired commit of the direct dependency, commit this change and then update all submodules:
If your driver uses another driver as a static dependency via git submodule
which in turn includes a sinqMotor submodule, it is not necessary to specify the
version of sinqMotor. Instead, specify the desired commit of the direct
dependency, commit this change and then update all submodules:
- `cd turboPmac`
- `git checkout 1.0`

View File

@@ -41,6 +41,7 @@ record(motor,"$(INSTR)$(M)")
field(PINI,"NO")
field(DHLM, "$(DHLM=0)")
field(DLLM, "$(DLLM=0)")
field(SPDB, "$(SPDB=0)")
field(TWV,"1")
field(RTRY,"0")
field(RDBD, "$(RDBD=10e300)") # Suppress retries and overshoot stop commands
@@ -58,7 +59,7 @@ record(calc, "$(INSTR)$(M):StatusProblem")
# If the value of this PV is 0, the according axis is currently disconnected from the controller.
# Trying to give commands to a disconnected axis will result in an error message in the IOC shell
# This record is coupled to the parameter library via motorConnected_ -> MOTOR_CONNECTED.
# This record is coupled to the parameter library via motorConnected -> MOTOR_CONNECTED.
record(longin, "$(INSTR)$(M):Connected")
{
field(DTYP, "asynInt32")
@@ -79,7 +80,7 @@ record(longout, "$(INSTR)$(M):Reset") {
# This PV allows force-stopping the motor record from within the driver by setting
# the motorForceStop_ value in the parameter library to 1. It should be reset to 0 by the driver afterwards.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorForceStop_ -> MOTOR_FORCE_STOP.
# This record is coupled to the parameter library via motorForceStop -> MOTOR_FORCE_STOP.
record(longin, "$(INSTR)$(M):StopRBV")
{
field(DTYP, "asynInt32")
@@ -108,7 +109,7 @@ record(ao,"$(INSTR)$(M):RecResolution") {
# This record contains messages from the driver (usually error messages).
# The macro MSGTEXTSIZE can be used to set the maximum length of the message.
# if not provided, a default value of 200 is used.
# This record is coupled to the parameter library via motorMessageText_ -> MOTOR_MESSAGE_TEXT.
# This record is coupled to the parameter library via motorMessageText -> MOTOR_MESSAGE_TEXT.
record(waveform, "$(INSTR)$(M)-MsgTxt") {
field(DTYP, "asynOctetRead")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_MESSAGE_TEXT")
@@ -120,7 +121,7 @@ record(waveform, "$(INSTR)$(M)-MsgTxt") {
# User-writable switch which disables the motor for an input of zero and enables
# it otherwise. Some motors can't be disabled in certain states (e.g. during
# movement). This behaviour has to be implemented inside the driver.
# This record is coupled to the parameter library via motorEnable_ -> MOTOR_ENABLE.
# This record is coupled to the parameter library via motorEnable -> MOTOR_ENABLE.
record(longout, "$(INSTR)$(M):Enable") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ENABLE")
@@ -128,7 +129,7 @@ record(longout, "$(INSTR)$(M):Enable") {
}
# Readback value which returns 1 if the motor is disabled and 0 otherwise.
# This record is coupled to the parameter library via motorEnableRBV_ -> MOTOR_ENABLE_RBV.
# This record is coupled to the parameter library via motorEnableRBV -> MOTOR_ENABLE_RBV.
record(longin, "$(INSTR)$(M):EnableRBV") {
field(DTYP, "asynInt32")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ENABLE_RBV")
@@ -139,7 +140,7 @@ record(longin, "$(INSTR)$(M):EnableRBV") {
# Some (older) motors cannot be disabled. This property has to be specified in
# the driver by setting the corresponding parameter library entry motorCanDisable_
# to 0 (its default value is 1).
# This record is coupled to the parameter library via motorCanDisable_ -> MOTOR_CAN_DISABLE.
# This record is coupled to the parameter library via motorCanDisable -> MOTOR_CAN_DISABLE.
record(longin, "$(INSTR)$(M):CanDisable") {
field(DTYP, "asynInt32")
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_CAN_DISABLE")
@@ -150,7 +151,7 @@ record(longin, "$(INSTR)$(M):CanDisable") {
# For some motors, the user might be allowed to adjust the speed within the
# limits specified in the motor record as VBAS and VMAX. This functionality can
# be enabled by setting CANSETSPEED to 1. It is disabled by default.
# This record is coupled to the parameter library via motorCanSetSpeed_ -> MOTOR_CAN_SET_SPEED.
# This record is coupled to the parameter library via motorCanSetSpeed -> MOTOR_CAN_SET_SPEED.
record(longout, "$(INSTR)$(M):CanSetSpeed") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_CAN_SET_SPEED")
@@ -187,7 +188,7 @@ record(longout, "$(INSTR)$(M):EnableMovWatchdog") {
# go into a "limits hit" error state. To prevent this, this value allows adding
# a small offset in EGU, which is subtracted from the high limit and added to the
# low limit.
# This record is coupled to the parameter library via motorLimitsOffset_ -> MOTOR_LIMITS_OFFSET.
# This record is coupled to the parameter library via motorLimitsOffset -> MOTOR_LIMITS_OFFSET.
record(ao, "$(INSTR)$(M):LimitsOffset") {
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_LIMITS_OFFSET")
@@ -200,7 +201,7 @@ record(ao, "$(INSTR)$(M):LimitsOffset") {
# and pushes it to the motor record field "DHLM". This can be used to read limits
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorHighLimitFromDriver_ -> MOTOR_HIGH_LIMIT_FROM_DRIVER.
# This record is coupled to the parameter library via motorHighLimitFromDriver -> MOTOR_HIGH_LIMIT_FROM_DRIVER.
record(ai, "$(INSTR)$(M):DHLM_RBV")
{
field(DTYP, "asynFloat64")
@@ -221,7 +222,7 @@ record(ao, "$(INSTR)$(M):PushDHLM2Field") {
# and pushes it to the motor record field "DLLM". This can be used to read limits
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorLowLimitFromDriver_ -> MOTOR_LOW_LIMIT_FROM_DRIVER.
# This record is coupled to the parameter library via motorLowLimitFromDriver -> MOTOR_LOW_LIMIT_FROM_DRIVER.
record(ai, "$(INSTR)$(M):DLLM_RBV")
{
field(DTYP, "asynFloat64")
@@ -238,11 +239,32 @@ record(ao, "$(INSTR)$(M):PushDLLM2Field") {
field(PINI, "NO")
}
# This record pair reads the parameter library value for "motorPositionDeadband"
# and pushes it to the motor record field "SPDP". This can be used to the position
# deadband from the hardware
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorPositionDeadband -> MOTOR_POSITION_DEADBAND.
record(ai, "$(INSTR)$(M):SPDB_RBV")
{
field(DTYP, "asynFloat64")
field(VAL, "$(SPDP=0)")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_POSITION_DEADBAND")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):PushSPDB2Field")
field(PINI, "NO")
}
record(ao, "$(INSTR)$(M):PushSPDB2Field") {
field(DOL, "$(INSTR)$(M):SPDB_RBV NPP")
field(OUT, "$(INSTR)$(M).SPDB")
field(OMSL, "closed_loop")
field(PINI, "NO")
}
# This record pair reads the parameter library value for "motorVeloFromDriver_"
# and pushes it to the motor record field "VELO". This can be used to read the speed value
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorVeloFromDriver_ -> MOTOR_VELO_FROM_DRIVER.
# This record is coupled to the parameter library via motorVeloFromDriver -> MOTOR_VELO_FROM_DRIVER.
record(ai, "$(INSTR)$(M):VELO_RBV")
{
field(DTYP, "asynFloat64")
@@ -260,7 +282,7 @@ record(ao, "$(INSTR)$(M):PushVELO2Field") {
# and pushes it to the motor record field "VBAS". This can be used to read the lower speed limit
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorVbasFromDriver_ -> MOTOR_VBAS_FROM_DRIVER.
# This record is coupled to the parameter library via motorVbasFromDriver -> MOTOR_VBAS_FROM_DRIVER.
record(ai, "$(INSTR)$(M):VBAS_RBV")
{
field(DTYP, "asynFloat64")
@@ -278,7 +300,7 @@ record(ao, "$(INSTR)$(M):PushVBAS2Field") {
# and pushes it to the motor record field "VMAX". This can be used to read the upper speed limit
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorVmaxFromDriver_ -> MOTOR_VMAX_FROM_DRIVER.
# This record is coupled to the parameter library via motorVmaxFromDriver -> MOTOR_VMAX_FROM_DRIVER.
record(ai, "$(INSTR)$(M):VMAX_RBV")
{
field(DTYP, "asynFloat64")
@@ -296,7 +318,7 @@ record(ao, "$(INSTR)$(M):PushVMAX2Field") {
# and pushes it to the motor record field "ACCL". This can be used to read the acceleration
# from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorAcclFromDriver_ -> MOTOR_ACCL_FROM_DRIVER.
# This record is coupled to the parameter library via motorAcclFromDriver -> MOTOR_ACCL_FROM_DRIVER.
record(ai, "$(INSTR)$(M):ACCL_RBV")
{
field(DTYP, "asynFloat64")
@@ -314,8 +336,9 @@ record(ao, "$(INSTR)$(M):PushACCL2Field") {
# codes and can be converted to chars in order to get the encoder type.
# EPICS prepends the ASCII code with 80
# The following encoder types are defined:
# - "Absolute encoder" (array 80 65 98 115 111 108 117 116 101 32 101 110 99 111 100 101 114)
# - "Incremental encoder" (array 80 73 110 99 114 101 109 101 110 116 97 108 32 101 110 99 111 100 101 114)
# - "absolute" (array 80 97 98 115 111 108 117 116 101)
# - "incremental" (array 80 105 110 99 114 101 109 101 110 116 97 108)
# - "none" (array 80 110 111 110 101)
# This record is coupled to the parameter library via encoderType -> ENCODER_TYPE.
record(waveform, "$(INSTR)$(M):EncoderType") {
field(DTYP, "asynOctetRead")

View File

@@ -5,21 +5,19 @@
msgPrintControlKey::msgPrintControlKey(char *controller, int axisNo,
const char *functionName, int line,
size_t maxRepetitions) {
controller_ = controller;
axisNo_ = axisNo;
line_ = line;
functionName_ = functionName;
maxRepetitions_ = maxRepetitions;
}
size_t maxRepetitions)
: controller_(controller), axisNo_(axisNo), functionName_(functionName),
line_(line), maxRepetitions_(maxRepetitions) {}
void msgPrintControlKey::format(char *buffer, size_t bufferSize) {
snprintf(buffer, bufferSize, "controller %s, axis %d, function %s, line %d",
controller_.c_str(), axisNo_, functionName_, line_);
controller_.c_str(), axisNo_, functionName_.c_str(), line_);
}
// =============================================================================
msgPrintControl::msgPrintControl() : map_(), suffix_{} {}
msgPrintControl::~msgPrintControl() = default;
bool msgPrintControl::shouldBePrinted(msgPrintControlKey &key, bool wantToPrint,
@@ -78,8 +76,8 @@ bool msgPrintControl::shouldBePrinted(msgPrintControlKey &key, bool wantToPrint,
pasynUser, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError "
"associated with key \"%s\" has been resolved.\n",
key.controller_.c_str(), key.axisNo_, key.functionName_,
key.line_, formattedKey);
key.controller_.c_str(), key.axisNo_,
key.functionName_.c_str(), key.line_, formattedKey);
}
map_[key] = 0;
}
@@ -92,8 +90,8 @@ bool msgPrintControl::shouldBePrinted(char *portName, int axisNo,
const char *functionName, int line,
bool wantToPrint, asynUser *pasynUser,
size_t maxRepetitions) {
msgPrintControlKey key =
msgPrintControlKey(portName, axisNo, functionName, __LINE__);
msgPrintControlKey key = msgPrintControlKey(portName, axisNo, functionName,
line, maxRepetitions);
return shouldBePrinted(key, wantToPrint, pasynUser);
}
@@ -107,7 +105,7 @@ void msgPrintControl::resetCount(msgPrintControlKey &key, asynUser *pasynUser) {
"Controller \"%s\", axis %d => %s, line %d\nError "
"associated with key \"%s\" has been resolved.\n",
key.controller_.c_str(), key.axisNo_,
key.functionName_, key.line_, formattedKey);
key.functionName_.c_str(), key.line_, formattedKey);
}
map_[key] = 0;
}

View File

@@ -5,8 +5,15 @@
#define DefaultMaxRepetitions 4
#include <asynDriver.h>
#include <macros.h>
// The EPICS libaries do not follow -Weffc++
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include "asynDriver.h"
#include "macros.h"
#pragma GCC diagnostic pop
#include <string.h>
#include <string>
#include <unordered_map>
@@ -18,12 +25,23 @@
*/
class HIDDEN msgPrintControlKey {
public:
msgPrintControlKey(char *controller_, int axisNo, const char *fileName,
int line, size_t maxRepetitions = DefaultMaxRepetitions);
bool operator==(const msgPrintControlKey &other) const {
return axisNo_ == other.axisNo_ && line_ == other.line_ &&
functionName_ == other.functionName_ &&
controller_ == other.controller_;
}
void format(char *buffer, size_t bufferSize);
std::string controller_;
// -1 indicates a non-axis specific message
int axisNo_;
const char *functionName_;
std::string functionName_;
int line_;
/**
@@ -32,17 +50,6 @@ class HIDDEN msgPrintControlKey {
*
*/
size_t maxRepetitions_;
msgPrintControlKey(char *controller_, int axisNo, const char *fileName,
int line, size_t maxRepetitions = DefaultMaxRepetitions);
bool operator==(const msgPrintControlKey &other) const {
return axisNo_ == other.axisNo_ && line_ == other.line_ &&
strcmp(functionName_, other.functionName_) == 0 &&
controller_ == other.controller_;
}
void format(char *buffer, size_t bufferSize);
};
/**
@@ -55,7 +62,7 @@ template <> struct hash<msgPrintControlKey> {
// Combine the hashes of the members (x and y)
size_t h1 = std::hash<std::string>{}(obj.controller_);
size_t h2 = hash<int>{}(obj.axisNo_);
size_t h3 = std::hash<const char *>{}(obj.functionName_);
size_t h3 = std::hash<std::string>{}(obj.functionName_);
size_t h4 = hash<int>{}(obj.line_);
// Combine the hashes (simple XOR and shifting technique)
return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3);
@@ -85,6 +92,12 @@ template <> struct hash<msgPrintControlKey> {
*/
class HIDDEN msgPrintControl {
public:
/**
* @brief Construct a new msgPrintControl object
*
*/
msgPrintControl();
/**
* @brief Destroy the msgPrintControl object
*

View File

@@ -1,12 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "sinqAxis.h"
// The EPICS libaries do not follow -Weffc++
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include "epicsExport.h"
#include "iocsh.h"
#include "msgPrintControl.h"
#include "sinqController.h"
#include <epicsTime.h>
#include <errlog.h>
#pragma GCC diagnostic pop
#include "msgPrintControl.h"
#include "sinqAxis.h"
#include "sinqController.h"
#include <math.h>
#include <unistd.h>
@@ -31,21 +38,22 @@ struct sinqAxisImpl {
};
sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
: asynMotorAxis((asynMotorController *)pC, axisNo), pC_(pC) {
: asynMotorAxis((asynMotorController *)pC, axisNo), pC_(pC), pSinqA_([] {
epicsTimeStamp lastPollTime;
epicsTimeGetCurrent(&lastPollTime);
return std::make_unique<sinqAxisImpl>(sinqAxisImpl{
.expectedArrivalTime = 0,
.offsetMovTimeout = 30,
.scaleMovTimeout = 2.0,
.watchdogMovActive = false,
.targetPosition = 0.0,
.wasMoving = false,
.lastPollTime = lastPollTime,
});
}()) {
asynStatus status = asynSuccess;
epicsTimeStamp lastPollTime;
epicsTimeGetCurrent(&lastPollTime);
pSinqA_ = std::make_unique<sinqAxisImpl>(
(sinqAxisImpl){.expectedArrivalTime = 0,
.offsetMovTimeout = 30,
.scaleMovTimeout = 2.0,
.watchdogMovActive = false,
.targetPosition = 0.0,
.wasMoving = false,
.lastPollTime = lastPollTime});
/*
This check is also done in asynMotorAxis, but there the IOC continues
running even though the configuration is incorrect. When failing this check,
@@ -283,9 +291,12 @@ asynStatus sinqAxis::forcedPoll(bool *moving) {
pSinqA_->wasMoving = *moving;
}
// Check and update the watchdog
if (checkMovTimeoutWatchdog(*moving) != asynSuccess) {
return asynError;
// Check and update the watchdog as well as the general poll status IF the
// poll did not fail already.
if (poll_status == asynSuccess) {
if (checkMovTimeoutWatchdog(*moving) != asynSuccess) {
poll_status = asynError;
}
}
// According to the function documentation of asynMotorAxis::poll, this
@@ -315,7 +326,12 @@ asynStatus sinqAxis::forcedPoll(bool *moving) {
return poll_status;
}
asynStatus sinqAxis::doPoll(bool *moving) { return asynSuccess; }
asynStatus sinqAxis::doPoll(bool *moving) {
// Suppress unused variable warning - this is just a default fallback
// function.
(void)moving;
return asynSuccess;
}
asynStatus sinqAxis::move(double position, int relative, double minVelocity,
double maxVelocity, double acceleration) {
@@ -353,6 +369,13 @@ asynStatus sinqAxis::move(double position, int relative, double minVelocity,
asynStatus sinqAxis::doMove(double position, int relative, double minVelocity,
double maxVelocity, double acceleration) {
// Suppress unused variable warning - this is just a default fallback
// function.
(void)position;
(void)relative;
(void)minVelocity;
(void)maxVelocity;
(void)acceleration;
return asynSuccess;
}
@@ -396,6 +419,12 @@ asynStatus sinqAxis::home(double minVelocity, double maxVelocity,
asynStatus sinqAxis::doHome(double minVelocity, double maxVelocity,
double acceleration, int forwards) {
// Suppress unused variable warning - this is just a default fallback
// function.
(void)minVelocity;
(void)maxVelocity;
(void)acceleration;
(void)forwards;
return asynSuccess;
}
@@ -417,7 +446,12 @@ asynStatus sinqAxis::reset() {
asynStatus sinqAxis::doReset() { return asynError; }
asynStatus sinqAxis::enable(bool on) { return asynSuccess; }
asynStatus sinqAxis::enable(bool on) {
// Suppress unused variable warning - this is just a default fallback
// function.
(void)on;
return asynSuccess;
}
asynStatus sinqAxis::motorPosition(double *motorPos) {
asynStatus status = asynSuccess;
@@ -479,9 +513,9 @@ asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
"vmax=%lf.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
vbas, vmax);
setAxisParamChecked(
this, motorMessageText,
"Lower speed limit must not be smaller than upper speed limit");
setAxisParamChecked(this, motorMessageText,
"Lower speed limit must not be smaller than "
"upper speed limit. Please call the support.");
return asynError;
}
if (velo < vbas || velo > vmax) {
@@ -492,8 +526,10 @@ asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
velo, vbas, vmax);
setAxisParamChecked(this, motorMessageText,
"Speed is not inside limits");
setAxisParamChecked(
this, motorMessageText,
"Speed is not inside limits. Set a new valid speed and try "
"to move the motor. Otherwise, please call the support.");
return asynError;
}
@@ -706,8 +742,9 @@ static const iocshArg setWatchdogEnabledArg2 = {
"Enabling / disabling the watchdog", iocshArgInt};
static const iocshArg *const setWatchdogEnabledArgs[] = {
&setWatchdogEnabledArg0, &setWatchdogEnabledArg1, &setWatchdogEnabledArg2};
static const iocshFuncDef setWatchdogEnabledDef = {"setWatchdogEnabled", 3,
setWatchdogEnabledArgs};
static const iocshFuncDef setWatchdogEnabledDef = {
"setWatchdogEnabled", 3, setWatchdogEnabledArgs,
"Set to 0 to disable the watchdog and to any other value to enable it."};
static void setWatchdogEnabledCallFunc(const iocshArgBuf *args) {
setWatchdogEnabled(args[0].sval, args[1].ival, args[2].ival);
@@ -753,8 +790,9 @@ static const iocshArg setOffsetMovTimeoutArg2 = {"Offset timeout for movement",
static const iocshArg *const setOffsetMovTimeoutArgs[] = {
&setOffsetMovTimeoutArg0, &setOffsetMovTimeoutArg1,
&setOffsetMovTimeoutArg2};
static const iocshFuncDef setOffsetMovTimeoutDef = {"setOffsetMovTimeout", 3,
setOffsetMovTimeoutArgs};
static const iocshFuncDef setOffsetMovTimeoutDef = {
"setOffsetMovTimeout", 3, setOffsetMovTimeoutArgs,
"Specify an offset (in seconds) for the movement timeout watchdog"};
static void setOffsetMovTimeoutCallFunc(const iocshArgBuf *args) {
setOffsetMovTimeout(args[0].sval, args[1].ival, args[2].dval);
@@ -800,8 +838,9 @@ static const iocshArg setScaleMovTimeoutArg2 = {
"Multiplier for calculated move time", iocshArgDouble};
static const iocshArg *const setScaleMovTimeoutArgs[] = {
&setScaleMovTimeoutArg0, &setScaleMovTimeoutArg1, &setScaleMovTimeoutArg2};
static const iocshFuncDef setScaleMovTimeoutDef = {"setScaleMovTimeout", 3,
setScaleMovTimeoutArgs};
static const iocshFuncDef setScaleMovTimeoutDef = {
"setScaleMovTimeout", 3, setScaleMovTimeoutArgs,
"Set a scaling factor for the maximum expected movement time."};
static void setScaleMovTimeoutCallFunc(const iocshArgBuf *args) {
setScaleMovTimeout(args[0].sval, args[1].ival, args[2].dval);

View File

@@ -8,8 +8,16 @@ Stefan Mathis, November 2024
#ifndef sinqAxis_H
#define sinqAxis_H
// The EPICS libaries do not follow -Weffc++
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include "asynMotorAxis.h"
#include <macros.h>
#include "macros.h"
#pragma GCC diagnostic pop
#include <memory>
#include <type_traits>
@@ -32,6 +40,13 @@ class HIDDEN sinqAxis : public asynMotorAxis {
*/
~sinqAxis();
/**
* @brief Delete the copy and copy assignment constructors, because this
* class should not be copied (it is tied to hardware!)
*/
sinqAxis(const sinqAxis &) = delete;
sinqAxis &operator=(const sinqAxis &) = delete;
/**
* @brief Check if a poll should be performed. If yes, call `forcedPoll`.
*
@@ -435,8 +450,10 @@ class HIDDEN sinqAxis : public asynMotorAxis {
void setTargetPosition(double targetPosition);
private:
std::unique_ptr<sinqAxisImpl> pSinqA_;
// Ordering matters because pC_ is initialized before pSinqA_ in the
// constructor
sinqController *pC_;
std::unique_ptr<sinqAxisImpl> pSinqA_;
};
// =============================================================================
@@ -606,7 +623,7 @@ template <typename A, typename C>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), int *readValue,
const char *callerFunctionName, int lineNumber,
size_t msgSize, TypeTag<int>) {
size_t /*msgSize*/, TypeTag<int>) {
int indexValue = (controller->*func)();
asynStatus status =
controller->getIntegerParam(axis->axisNo(), indexValue, readValue);
@@ -634,7 +651,7 @@ template <typename A, typename C>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), double *readValue,
const char *callerFunctionName, int lineNumber,
size_t msgSize, TypeTag<double>) {
size_t /*msgSize*/, TypeTag<double>) {
int indexValue = (controller->*func)();
asynStatus status =
controller->getDoubleParam(axis->axisNo(), indexValue, readValue);
@@ -665,7 +682,7 @@ template <typename A, typename C>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), std::string *readValue,
const char *callerFunctionName, int lineNumber,
size_t msgSize, TypeTag<std::string>) {
size_t /*msgSize*/, TypeTag<std::string>) {
int indexValue = (controller->*func)();
// Convert the pointer to a reference, since getStringParam expects the

View File

@@ -1,15 +1,22 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "sinqController.h"
// The EPICS libaries do not follow -Weffc++
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include "asynMotorController.h"
#include "asynOctetSyncIO.h"
#include "epicsExport.h"
#include "iocsh.h"
#include "msgPrintControl.h"
#include "sinqAxis.h"
#include <deque>
#include <errlog.h>
#include <initHooks.h>
#pragma GCC diagnostic pop
#include "msgPrintControl.h"
#include "sinqAxis.h"
#include "sinqController.h"
#include <deque>
#include <unordered_map>
#include <vector>
@@ -92,6 +99,7 @@ struct sinqControllerImpl {
int motorAcclFromDriver;
int motorHighLimitFromDriver;
int motorLowLimitFromDriver;
int motorPositionDeadband;
int adaptivePolling;
int encoderType;
};
@@ -111,23 +119,40 @@ sinqController::sinqController(const char *portName,
0, // No additional interfaces beyond those in base class
0, // No additional callback interfaces beyond those in base class
ASYN_CANBLOCK | ASYN_MULTIDEVICE,
1, // autoconnect
0, 0) // Default priority and stack size
{
1, // autoconnect
0, 0), // Default priority and stack size
pSinqC_(std::make_unique<sinqControllerImpl>((sinqControllerImpl){
.outstandingForcedFastPolls = 0,
.pasynOctetSyncIOipPort = nullptr,
.msgPrintC = msgPrintControl(),
.comTimeoutWindow = 3600,
.maxNumberTimeouts = 60,
.timeoutEvents = {},
.maxSubsequentTimeouts = 10,
.maxSubsequentTimeoutsExceeded = false,
.motorMessageText = 0,
.motorReset = 0,
.motorEnable = 0,
.motorEnableRBV = 0,
.motorCanDisable = 0,
.motorEnableMovWatchdog = 0,
.motorCanSetSpeed = 0,
.motorLimitsOffset = 0,
.motorForceStop = 0,
.motorConnected = 0,
.motorVeloFromDriver = 0,
.motorVbasFromDriver = 0,
.motorVmaxFromDriver = 0,
.motorAcclFromDriver = 0,
.motorHighLimitFromDriver = 0,
.motorLowLimitFromDriver = 0,
.motorPositionDeadband = 0,
.adaptivePolling = 0,
.encoderType = 0,
})) {
asynStatus status = asynSuccess;
// The paramLib indices are populated with the calls to createParam
pSinqC_ = std::make_unique<sinqControllerImpl>(
(sinqControllerImpl){.outstandingForcedFastPolls = 0,
.pasynOctetSyncIOipPort = nullptr,
.msgPrintC = msgPrintControl(),
.comTimeoutWindow = 3600,
.maxNumberTimeouts = 60,
.timeoutEvents = {},
.maxSubsequentTimeouts = 10,
.maxSubsequentTimeoutsExceeded = false});
// Store the poll period information. The poller itself will be started
// later (after the IOC is running in epicsInithookFunction)
movingPollPeriod_ = movingPollPeriod;
@@ -267,6 +292,17 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status = createParam("MOTOR_POSITION_DEADBAND", asynParamFloat64,
&pSinqC_->motorPositionDeadband);
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);
}
status = createParam("MOTOR_ENABLE_MOV_WATCHDOG", asynParamInt32,
&pSinqC_->motorEnableMovWatchdog);
if (status != asynSuccess) {
@@ -403,10 +439,12 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
sinqAxis *axis = getSinqAxis(pasynUser);
if (axis == nullptr) {
int axisNo;
getAddress(pasynUser, &axisNo);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not an "
"instance of sinqAxis",
portName, axis->axisNo(), __PRETTY_FUNCTION__, __LINE__);
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
@@ -426,10 +464,12 @@ asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
sinqAxis *axis = getSinqAxis(pasynUser);
if (axis == nullptr) {
int axisNo;
getAddress(pasynUser, &axisNo);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not an "
"instance of sinqAxis",
portName, axis->axisNo(), __PRETTY_FUNCTION__, __LINE__);
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
@@ -459,7 +499,7 @@ asynStatus sinqController::couldNotParseResponse(const char *command,
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not an "
"instance of sinqAxis",
portName, axis->axisNo(), __PRETTY_FUNCTION__, __LINE__);
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
@@ -699,6 +739,9 @@ int sinqController::motorHighLimitFromDriver() {
int sinqController::motorLowLimitFromDriver() {
return pSinqC_->motorLowLimitFromDriver;
}
int sinqController::motorPositionDeadband() {
return pSinqC_->motorPositionDeadband;
}
int sinqController::adaptivePolling() { return pSinqC_->adaptivePolling; }
int sinqController::encoderType() { return pSinqC_->encoderType; }
@@ -809,7 +852,8 @@ static const iocshArg *const setThresholdComTimeoutArgs[] = {
&setThresholdComTimeoutArg0, &setThresholdComTimeoutArg1,
&setThresholdComTimeoutArg2};
static const iocshFuncDef setThresholdComTimeoutDef = {
"setThresholdComTimeout", 3, setThresholdComTimeoutArgs};
"setThresholdComTimeout", 3, setThresholdComTimeoutArgs,
"Set the communication timeout threshold in seconds"};
static void setThresholdComTimeoutCallFunc(const iocshArgBuf *args) {
setThresholdComTimeout(args[0].sval, args[1].ival, args[2].ival);
@@ -870,7 +914,9 @@ static const iocshArg SetMaxSubsequentTimeoutsArg1 = {
static const iocshArg *const SetMaxSubsequentTimeoutsArgs[] = {
&SetMaxSubsequentTimeoutsArg0, &SetMaxSubsequentTimeoutsArg1};
static const iocshFuncDef setMaxSubsequentTimeoutsDef = {
"setMaxSubsequentTimeouts", 2, SetMaxSubsequentTimeoutsArgs};
"setMaxSubsequentTimeouts", 2, SetMaxSubsequentTimeoutsArgs,
"Set the maximum number of subsequent timeouts before the user receives an "
"error message"};
static void setMaxSubsequentTimeoutsCallFunc(const iocshArgBuf *args) {
setMaxSubsequentTimeouts(args[0].sval, args[1].ival);
}
@@ -924,13 +970,15 @@ asynStatus setForcedFastPolls(const char *portName, int forcedFastPolls) {
static const iocshArg SetForcedFastPollsArg0 = {"Controller name (e.g. mcu1)",
iocshArgString};
static const iocshArg SetForcedFastPollsArg1 = {
"Number of fast polls after \"waking\" the poller (e.g. after issueing a "
"Number of fast polls after \"waking\" the poller (e.g. after issuing a "
"move command).",
iocshArgInt};
static const iocshArg *const SetForcedFastPollsArgs[] = {
&SetForcedFastPollsArg0, &SetForcedFastPollsArg1};
static const iocshFuncDef setForcedFastPollsDef = {"setForcedFastPolls", 2,
SetForcedFastPollsArgs};
static const iocshFuncDef setForcedFastPollsDef = {
"setForcedFastPolls", 2, SetForcedFastPollsArgs,
"Set the number of fast polls after \"waking\" the poller (e.g. after "
"issuing a move command)."};
static void setForcedFastPollsCallFunc(const iocshArgBuf *args) {
setForcedFastPolls(args[0].sval, args[1].ival);
}

View File

@@ -9,10 +9,18 @@ Stefan Mathis, November 2024
#ifndef sinqController_H
#define sinqController_H
// The EPICS libaries do not follow -Weffc++
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include "asynMotorController.h"
#include "msgPrintControl.h"
#include <initHooks.h>
#include <macros.h>
#pragma GCC diagnostic pop
#include <memory>
#define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER"
@@ -58,6 +66,13 @@ class HIDDEN sinqController : public asynMotorController {
*/
virtual ~sinqController(void);
/**
* @brief Delete the copy and copy assignment constructors, because this
* class should not be copied (it is tied to hardware!)
*/
sinqController(const sinqController &) = delete;
sinqController &operator=(const sinqController &) = delete;
/**
* @brief Overloaded function of asynMotorController
*
@@ -315,6 +330,7 @@ class HIDDEN sinqController : public asynMotorController {
int motorAcclFromDriver();
int motorHighLimitFromDriver();
int motorLowLimitFromDriver();
int motorPositionDeadband();
int adaptivePolling();
int encoderType();