Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e234d05815 | |||
| 1d1e562490 | |||
| e2b4c330a7 | |||
| a6ca695513 | |||
| 59a5ba452f | |||
| 6dc2b131f7 | |||
| 902b18d038 | |||
| 0e10bcf69d | |||
| cb4adb068c | |||
| d7c9d009ee | |||
| 3ab40a8bf5 | |||
| 0478854007 | |||
| 9a32532c22 | |||
| cff64f5ecf | |||
| 7a0de4e9d9 | |||
| 566728c57c | |||
| 8689c79f19 | |||
| 7ed054d075 | |||
| 7965dd3b2e | |||
| c19e4845e4 | |||
| 4d27783062 | |||
| 5273feef6c | |||
| cccfc79860 | |||
| c65a8de5dd | |||
| 1910eda0b1 | |||
| 977016bdb4 | |||
| ed77125378 | |||
| 4a0c09bd7f | |||
| 1fe21ec192 | |||
| 2fd4851313 | |||
| 55a9fe6f3e | |||
| e618b39687 | |||
| 41dfd1de5a | |||
| 07cab3ac2a | |||
| e194736206 | |||
| 30af284f5d | |||
| 6069aa9194 | |||
| c475beee66 | |||
| b1fe452ed6 | |||
| d395c7bbb7 | |||
| a6f2890c76 | |||
| fef61bc804 | |||
| 3d984f26bc | |||
| 2f8ae23d57 | |||
| 603b3e77af | |||
| 31ff26cb78 | |||
| 43df40aaea | |||
| bdefc6090d | |||
| c2eca33ce8 | |||
| 87980e403c | |||
| b95e782ea8 | |||
| cd7cc75eb7 | |||
| 83aa437b6b |
23
.gitea/workflows/action.yaml
Normal file
23
.gitea/workflows/action.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Test And Build
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
Lint:
|
||||
runs-on: linepics
|
||||
steps:
|
||||
- name: checkout repo
|
||||
uses: actions/checkout@v4
|
||||
- name: cppcheck
|
||||
run: cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 src/*.cpp
|
||||
- name: formatting
|
||||
run: clang-format --style=file --Werror --dry-run src/*.cpp
|
||||
Build:
|
||||
runs-on: linepics
|
||||
steps:
|
||||
- name: checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
- run: |
|
||||
sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
|
||||
make install
|
||||
@@ -1,41 +0,0 @@
|
||||
default:
|
||||
image: docker.psi.ch:5000/sinqdev/sinqepics:latest
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- build
|
||||
- test
|
||||
|
||||
cppcheck:
|
||||
stage: lint
|
||||
script:
|
||||
- cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 src/*.cpp
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
tags:
|
||||
- sinq
|
||||
|
||||
formatting:
|
||||
stage: lint
|
||||
script:
|
||||
- clang-format --style=file --Werror --dry-run src/*.cpp
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
tags:
|
||||
- sinq
|
||||
|
||||
build_module:
|
||||
stage: build
|
||||
script:
|
||||
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
|
||||
- echo "LIBVERSION=${CI_COMMIT_TAG:-0.0.1}" >> Makefile
|
||||
- make install
|
||||
- cp -rT "/ioc/modules/sinqMotor/$(ls -U /ioc/modules/sinqMotor/ | head -1)" "./sinqMotor-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
|
||||
artifacts:
|
||||
name: "sinqMotor-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- "sinqMotor-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}/*"
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
tags:
|
||||
- sinq
|
||||
3
Makefile
3
Makefile
@@ -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
|
||||
|
||||
328
README.md
328
README.md
@@ -2,96 +2,78 @@
|
||||
|
||||
## 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 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
|
||||
|
||||
# Load libraries needed for the IOC
|
||||
require sinqMotor, 1.0.0
|
||||
require actualDriver, 1.2.0
|
||||
require exampleDriver, 1.0.0
|
||||
|
||||
# Define environment variables used later to parametrize the individual controllers
|
||||
epicsEnvSet("TOP","/ioc/sinq-ioc/sinqtest-ioc/")
|
||||
epicsEnvSet("INSTR","SQ:SINQTEST:")
|
||||
|
||||
# Include other scripts for the controllers 1 and 2
|
||||
< actualDriver.cmd
|
||||
< actualDriver.cmd
|
||||
< exampleDriver1.cmd
|
||||
< exampleDriver2.cmd
|
||||
|
||||
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 controller script "mcu1.cmd" looks like this:
|
||||
The script for controller 1 ("turboPmac1.cmd") for a Turbo PMAC (see https://git.psi.ch/sinq-epics-modules/turboPmac) has the following structure. The scripts for other controller types can be found in the README.md of their respective repositories.
|
||||
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","actualDriver1")
|
||||
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
|
||||
@@ -99,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);
|
||||
@@ -112,21 +95,24 @@ 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.
|
||||
epicsEnvSet("SINQDBPATH","$(sinqMotor_DB)/sinqMotor.db")
|
||||
epicsEnvSet("SINQDBPATH","$(exampleDriver_DB)/sinqMotor.db")
|
||||
dbLoadTemplate("$(TOP)/$(DRIVER_PORT).substitutions", "INSTR=$(INSTR)$(DRIVER_PORT):,CONTROLLER=$(DRIVER_PORT)")
|
||||
epicsEnvSet("SINQDBPATH","$(actualDriver_DB)/turboPmac.db")
|
||||
epicsEnvSet("SINQDBPATH","$(exampleDriver_DB)/turboPmac.db")
|
||||
dbLoadTemplate("$(TOP)/$(DRIVER_PORT).substitutions", "INSTR=$(INSTR)$(DRIVER_PORT):,CONTROLLER=$(DRIVER_PORT)")
|
||||
dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(DRIVER_PORT),PORT=$(IP_PORT)")
|
||||
dbLoadRecords("$(exampleDriver_DB)/asynRecord.db","P=$(INSTR)$(DRIVER_PORT),PORT=$(IP_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, "mcu1.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)"
|
||||
{
|
||||
@@ -142,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
|
||||
|
||||
@@ -202,68 +209,145 @@ 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
|
||||
|
||||
### Base classes
|
||||
### 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.
|
||||
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!
|
||||
|
||||
#### 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`.
|
||||
- `doMove`: This is an empty function which should be overwritten by concrete driver implementations.
|
||||
- `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 `doPoll` which performs some bookkeeping tasks before and after calling `doPoll`:
|
||||
- `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)?
|
||||
- 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!
|
||||
|
||||
- `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 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`):
|
||||
```
|
||||
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
|
||||
@@ -271,24 +355,54 @@ 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 some of 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 0.1`
|
||||
- `git checkout 1.0`
|
||||
- `cd ..`
|
||||
|
||||
Then, the fixation of the version to 0.1 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 0.1"`
|
||||
- `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:
|
||||
|
||||
- `cd turboPmac`
|
||||
- `git checkout 1.0`
|
||||
- `cd ..`
|
||||
- `git commit -m "Update turboPmac to 1.0"`
|
||||
- `git submodule update --init --recursive`
|
||||
|
||||
This will update sinqMotor to the version specified in the 1.0 commit of turboPmac.
|
||||
|
||||
@@ -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")
|
||||
|
||||
22
src/macros.h
Normal file
22
src/macros.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// Collection of macros used in sinqMotor and derived classes
|
||||
|
||||
#ifndef macros_H
|
||||
#define macros_H
|
||||
|
||||
/*
|
||||
The macro "HIDDEN" hides the symbol of the annotated class / function. This is
|
||||
useful to avoid symbol clashes when loading multiple shared libraries in a
|
||||
single IOC. To override the hiding, add `-DHIDDEN= ` to your compiler
|
||||
flags (in this case, the symbols will be exported with their default
|
||||
visibility).
|
||||
*/
|
||||
#ifndef HIDDEN
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
#define HIDDEN
|
||||
#else
|
||||
#define HIDDEN __attribute__((visibility("hidden")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// macros_H
|
||||
#endif
|
||||
@@ -5,21 +5,21 @@
|
||||
|
||||
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,
|
||||
asynUser *pasynUser) {
|
||||
|
||||
@@ -76,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;
|
||||
}
|
||||
@@ -90,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);
|
||||
}
|
||||
|
||||
@@ -105,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;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,15 @@
|
||||
|
||||
#define DefaultMaxRepetitions 4
|
||||
|
||||
#include <asynDriver.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>
|
||||
@@ -15,14 +23,25 @@
|
||||
* `msgPrintControl` on how to use this key.
|
||||
*
|
||||
*/
|
||||
class msgPrintControlKey {
|
||||
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_;
|
||||
|
||||
/**
|
||||
@@ -31,17 +50,6 @@ class 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);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -54,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);
|
||||
@@ -82,8 +90,20 @@ template <> struct hash<msgPrintControlKey> {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class msgPrintControl {
|
||||
class HIDDEN msgPrintControl {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new msgPrintControl object
|
||||
*
|
||||
*/
|
||||
msgPrintControl();
|
||||
|
||||
/**
|
||||
* @brief Destroy the msgPrintControl object
|
||||
*
|
||||
*/
|
||||
~msgPrintControl();
|
||||
|
||||
/**
|
||||
* @brief Checks if the error message associated with "key" has been printed
|
||||
* more than `this->maxRepetitions_` times in a row. If yes, returns false,
|
||||
|
||||
385
src/sinqAxis.cpp
385
src/sinqAxis.cpp
@@ -1,201 +1,64 @@
|
||||
// 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 "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>
|
||||
|
||||
// Generic fallback - if the compiler tries to compile this function, it fails.
|
||||
template <typename T>
|
||||
asynStatus setAxisParam(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), T writeValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
static_assert(
|
||||
sizeof(T) == 0,
|
||||
"no specialization of setAxisParam exists for the given type");
|
||||
return asynError;
|
||||
}
|
||||
#define getControllerMethod pController
|
||||
|
||||
template <>
|
||||
asynStatus setAxisParam<int>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), int writeValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
int indexValue = (axis->pController()->*func)();
|
||||
asynStatus status = axis->setIntegerParam(indexValue, writeValue);
|
||||
if (status != asynSuccess) {
|
||||
return axis->pController()->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
struct sinqAxisImpl {
|
||||
// Internal variables used in the movement timeout watchdog
|
||||
time_t expectedArrivalTime;
|
||||
time_t offsetMovTimeout;
|
||||
|
||||
template <>
|
||||
asynStatus setAxisParam<bool>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), bool writeValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
return setAxisParam(axis, indexName, func, static_cast<int>(writeValue),
|
||||
callerFunctionName, lineNumber);
|
||||
}
|
||||
double scaleMovTimeout;
|
||||
bool watchdogMovActive;
|
||||
// Store the motor target position for the movement time calculation
|
||||
double targetPosition;
|
||||
|
||||
template <>
|
||||
asynStatus
|
||||
setAxisParam<double>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), double writeValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
int indexValue = (axis->pController()->*func)();
|
||||
asynStatus status = axis->setDoubleParam(indexValue, writeValue);
|
||||
if (status != asynSuccess) {
|
||||
return axis->pController()->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
bool wasMoving;
|
||||
|
||||
template <>
|
||||
asynStatus setAxisParam<char *>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), char *writeValue,
|
||||
const char *callerFunctionName,
|
||||
int lineNumber) {
|
||||
int indexValue = (axis->pController()->*func)();
|
||||
asynStatus status = axis->setStringParam(indexValue, writeValue);
|
||||
if (status != asynSuccess) {
|
||||
return axis->pController()->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
template <>
|
||||
asynStatus setAxisParam<const char *>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(),
|
||||
const char *writeValue,
|
||||
const char *callerFunctionName,
|
||||
int lineNumber) {
|
||||
int indexValue = (axis->pController()->*func)();
|
||||
asynStatus status = axis->setStringParam(indexValue, writeValue);
|
||||
if (status != asynSuccess) {
|
||||
return axis->pController()->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
// Generic fallback - if the compiler tries to compile this function, it fails.
|
||||
template <typename T>
|
||||
asynStatus getAxisParam(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), T *readValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
static_assert(
|
||||
sizeof(T) == 0,
|
||||
"no specialization of getAxisParam exists for the given type");
|
||||
return asynError;
|
||||
}
|
||||
|
||||
template <>
|
||||
asynStatus getAxisParam<int>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), int *readValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
int indexValue = (axis->pController()->*func)();
|
||||
asynStatus status = axis->pController()->getIntegerParam(
|
||||
axis->axisNo(), indexValue, readValue);
|
||||
if (status != asynSuccess) {
|
||||
return axis->pController()->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
template <>
|
||||
asynStatus getAxisParam<bool>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), bool *readValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
return getAxisParam(axis, indexName, func, (int *)readValue,
|
||||
callerFunctionName, lineNumber);
|
||||
}
|
||||
|
||||
template <>
|
||||
asynStatus
|
||||
getAxisParam<double>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), double *readValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
int indexValue = (axis->pController()->*func)();
|
||||
asynStatus status = axis->pController()->getDoubleParam(
|
||||
axis->axisNo(), indexValue, readValue);
|
||||
if (status != asynSuccess) {
|
||||
return axis->pController()->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
template <>
|
||||
asynStatus getAxisParam<char>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), char *readValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
|
||||
int maxChars = 200;
|
||||
|
||||
int indexValue = (axis->pController()->*func)();
|
||||
asynStatus status = axis->pController()->getStringParam(
|
||||
axis->axisNo(), indexValue, maxChars, readValue);
|
||||
if (status != asynSuccess) {
|
||||
return axis->pController()->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
asynStatus getAxisParam(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), char (&readValue)[N],
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
// Decay the array to char*
|
||||
return getAxisParam<char>(axis, indexName, func,
|
||||
static_cast<char *>(readValue),
|
||||
callerFunctionName, lineNumber);
|
||||
}
|
||||
|
||||
template <>
|
||||
asynStatus
|
||||
getAxisParam<std::string>(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), std::string *readValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
int indexValue = (axis->pController()->*func)();
|
||||
|
||||
// Convert the pointer to a reference, since getStringParam expects the
|
||||
// latter.
|
||||
std::string &rReadValue = *readValue;
|
||||
|
||||
asynStatus status = axis->pController()->getStringParam(
|
||||
axis->axisNo(), indexValue, rReadValue);
|
||||
if (status != asynSuccess) {
|
||||
return axis->pController()->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
/*
|
||||
Store the time since the last poll
|
||||
*/
|
||||
epicsTimeStamp lastPollTime;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
watchdogMovActive_ = false;
|
||||
scaleMovTimeout_ = 2.0;
|
||||
offsetMovTimeout_ = 30;
|
||||
targetPosition_ = 0.0;
|
||||
wasMoving_ = false;
|
||||
|
||||
epicsTimeGetCurrent(&lastPollTime_);
|
||||
|
||||
// This check is also done in asynMotorAxis, but there the IOC continues
|
||||
// running even though the configuration is incorrect. When failing this
|
||||
// check, the IOC is stopped, since this is definitely a configuration
|
||||
// problem.
|
||||
/*
|
||||
This check is also done in asynMotorAxis, but there the IOC continues
|
||||
running even though the configuration is incorrect. When failing this check,
|
||||
the IOC is stopped, since this is definitely a configuration problem.
|
||||
*/
|
||||
if ((axisNo < 0) || (axisNo >= pC->numAxes())) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
@@ -313,15 +176,10 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
|
||||
}
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::poll(bool *moving) {
|
||||
sinqAxis::~sinqAxis() = default;
|
||||
|
||||
// Local variable declaration
|
||||
asynStatus pl_status = asynSuccess;
|
||||
asynStatus poll_status = asynSuccess;
|
||||
int homing = 0;
|
||||
asynStatus sinqAxis::poll(bool *moving) {
|
||||
int adaptivePolling = 0;
|
||||
char waitingMessage[pC_->MAXBUF_] = {0};
|
||||
char newMessage[pC_->MAXBUF_] = {0};
|
||||
|
||||
/*
|
||||
If adaptive polling is enabled:
|
||||
@@ -344,23 +202,34 @@ asynStatus sinqAxis::poll(bool *moving) {
|
||||
Check if both adaptive polling is enabled and no forced fast polls are still
|
||||
required.
|
||||
*/
|
||||
if (adaptivePolling != 0 && pC_->outstandingForcedFastPolls() == 0) {
|
||||
// Motor wasn't moving during the last poll
|
||||
if (!wasMoving_) {
|
||||
if (adaptivePolling != 0 && pC_->outstandingForcedFastPolls() == 0 &&
|
||||
!pSinqA_->wasMoving) {
|
||||
|
||||
// Add the idle poll period
|
||||
epicsTimeStamp earliestTimeNextPoll = lastPollTime_;
|
||||
epicsTimeAddSeconds(&earliestTimeNextPoll, pC_->idlePollPeriod());
|
||||
// Add the idle poll period
|
||||
epicsTimeStamp earliestTimeNextPoll = pSinqA_->lastPollTime;
|
||||
epicsTimeAddSeconds(&earliestTimeNextPoll, pC_->idlePollPeriod());
|
||||
|
||||
if (epicsTimeLessThanEqual(&earliestTimeNextPoll, &ts) == 0) {
|
||||
*moving = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
if (epicsTimeLessThanEqual(&earliestTimeNextPoll, &ts) == 0) {
|
||||
*moving = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
}
|
||||
return forcedPoll(moving);
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::forcedPoll(bool *moving) {
|
||||
|
||||
// Local variable declaration
|
||||
asynStatus pl_status = asynSuccess;
|
||||
asynStatus poll_status = asynSuccess;
|
||||
int homing = 0;
|
||||
char waitingMessage[pC_->MAXBUF_] = {0};
|
||||
char newMessage[pC_->MAXBUF_] = {0};
|
||||
|
||||
// Update the start time of the last poll
|
||||
lastPollTime_ = ts;
|
||||
epicsTimeStamp ts;
|
||||
epicsTimeGetCurrent(&ts);
|
||||
pSinqA_->lastPollTime = ts;
|
||||
|
||||
/*
|
||||
If the "motorMessageText" record currently contains an error message, it
|
||||
@@ -411,7 +280,7 @@ asynStatus sinqAxis::poll(bool *moving) {
|
||||
assumed that the homing procedure is done.
|
||||
*/
|
||||
getAxisParamChecked(this, motorStatusHome, &homing);
|
||||
if (homing == 1 && !(*moving) && wasMoving_) {
|
||||
if (homing == 1 && !(*moving) && pSinqA_->wasMoving) {
|
||||
setAxisParamChecked(this, motorStatusHome, false);
|
||||
setAxisParamChecked(this, motorStatusHomed, true);
|
||||
setAxisParamChecked(this, motorStatusAtHome, true);
|
||||
@@ -419,12 +288,15 @@ asynStatus sinqAxis::poll(bool *moving) {
|
||||
|
||||
// Update the wasMoving status
|
||||
if (pC_->outstandingForcedFastPolls() == 0) {
|
||||
wasMoving_ = *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
|
||||
@@ -454,7 +326,12 @@ asynStatus sinqAxis::poll(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) {
|
||||
@@ -467,7 +344,7 @@ asynStatus sinqAxis::move(double position, int relative, double minVelocity,
|
||||
|
||||
// Store the target position internally
|
||||
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
|
||||
targetPosition_ = position * motorRecRes;
|
||||
pSinqA_->targetPosition = position * motorRecRes;
|
||||
|
||||
status = doMove(position, relative, minVelocity, maxVelocity, acceleration);
|
||||
if (status != asynSuccess) {
|
||||
@@ -485,13 +362,20 @@ asynStatus sinqAxis::move(double position, int relative, double minVelocity,
|
||||
setAxisParamChecked(this, motorStatusAtHome, false);
|
||||
|
||||
// Needed for adaptive polling
|
||||
wasMoving_ = true;
|
||||
pSinqA_->wasMoving = true;
|
||||
|
||||
return pC_->callParamCallbacks();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -507,7 +391,7 @@ asynStatus sinqAxis::home(double minVelocity, double maxVelocity,
|
||||
setAxisParamChecked(this, motorStatusHome, true);
|
||||
setAxisParamChecked(this, motorStatusHomed, false);
|
||||
setAxisParamChecked(this, motorStatusAtHome, false);
|
||||
wasMoving_ = true;
|
||||
pSinqA_->wasMoving = true;
|
||||
|
||||
status = assertConnected();
|
||||
if (status != asynSuccess) {
|
||||
@@ -535,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;
|
||||
}
|
||||
|
||||
@@ -556,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;
|
||||
@@ -618,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) {
|
||||
@@ -631,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;
|
||||
}
|
||||
|
||||
@@ -681,7 +578,7 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
|
||||
time_t timeAccel = 0;
|
||||
|
||||
// Activate the watchdog
|
||||
watchdogMovActive_ = true;
|
||||
pSinqA_->watchdogMovActive = true;
|
||||
|
||||
/*
|
||||
NOTE: This function must not be called in the constructor (e.g. in order
|
||||
@@ -717,8 +614,9 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
|
||||
motorVelocity = motorVelocityRec * motorRecRes;
|
||||
if (pl_status == asynSuccess) {
|
||||
|
||||
timeContSpeed = std::ceil(
|
||||
std::fabs(targetPosition_ - motorPos) / motorVelocity);
|
||||
timeContSpeed =
|
||||
std::ceil(std::fabs(pSinqA_->targetPosition - motorPos) /
|
||||
motorVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,11 +632,11 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
|
||||
}
|
||||
|
||||
// Calculate the expected arrival time
|
||||
expectedArrivalTime_ =
|
||||
time(NULL) + offsetMovTimeout_ +
|
||||
scaleMovTimeout_ * (timeContSpeed + 2 * timeAccel);
|
||||
pSinqA_->expectedArrivalTime =
|
||||
time(NULL) + pSinqA_->offsetMovTimeout +
|
||||
pSinqA_->scaleMovTimeout * (timeContSpeed + 2 * timeAccel);
|
||||
} else {
|
||||
watchdogMovActive_ = false;
|
||||
pSinqA_->watchdogMovActive = false;
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
@@ -749,8 +647,8 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
|
||||
getAxisParamChecked(this, motorEnableMovWatchdog, &enableMovWatchdog);
|
||||
|
||||
// Not moving or watchdog not active / enabled
|
||||
if (enableMovWatchdog == 0 || !moving || !watchdogMovActive_) {
|
||||
watchdogMovActive_ = false;
|
||||
if (enableMovWatchdog == 0 || !moving || !pSinqA_->watchdogMovActive) {
|
||||
pSinqA_->watchdogMovActive = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
@@ -760,7 +658,7 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
|
||||
// Check if the expected time of arrival has been exceeded.
|
||||
if (expectedArrivalTime_ < time(NULL)) {
|
||||
if (pSinqA_->expectedArrivalTime < time(NULL)) {
|
||||
// Check the watchdog
|
||||
if (pC_->getMsgPrintControl().shouldBePrinted(key, true,
|
||||
pC_->pasynUser())) {
|
||||
@@ -768,7 +666,7 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nExceeded "
|
||||
"expected arrival time %ld (current time is %ld).\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
expectedArrivalTime_, time(NULL));
|
||||
pSinqA_->expectedArrivalTime, time(NULL));
|
||||
}
|
||||
|
||||
setAxisParamChecked(
|
||||
@@ -783,6 +681,26 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::setOffsetMovTimeout(time_t offsetMovTimeout) {
|
||||
pSinqA_->offsetMovTimeout = offsetMovTimeout;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::setScaleMovTimeout(time_t scaleMovTimeout) {
|
||||
pSinqA_->scaleMovTimeout = scaleMovTimeout;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
bool sinqAxis::wasMoving() { return pSinqA_->wasMoving; }
|
||||
|
||||
void sinqAxis::setWasMoving(bool wasMoving) { pSinqA_->wasMoving = wasMoving; }
|
||||
|
||||
double sinqAxis::targetPosition() { return pSinqA_->targetPosition; }
|
||||
|
||||
void sinqAxis::setTargetPosition(double targetPosition) {
|
||||
pSinqA_->targetPosition = targetPosition;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// IOC shell functions
|
||||
extern "C" {
|
||||
@@ -824,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);
|
||||
@@ -871,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);
|
||||
@@ -918,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);
|
||||
|
||||
408
src/sinqAxis.h
408
src/sinqAxis.h
@@ -8,11 +8,22 @@ 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 <epicsTime.h>
|
||||
#include "macros.h"
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
struct sinqAxisImpl;
|
||||
|
||||
class HIDDEN sinqAxis : public asynMotorAxis {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new sinqAxis object
|
||||
@@ -22,6 +33,41 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
*/
|
||||
sinqAxis(class sinqController *pC_, int axisNo);
|
||||
|
||||
/**
|
||||
* @brief Destroy the sinqAxis object
|
||||
*
|
||||
* This destructor is necessary in order to use the PIMPL idiom.
|
||||
*/
|
||||
~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`.
|
||||
*
|
||||
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)?
|
||||
- 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!
|
||||
*
|
||||
* @param moving Forwarded to `forcedPoll` or set to false
|
||||
(depending on whether `forcedPoll was called`).
|
||||
* @return asynStatus Forward the status of `forcedPoll` or set to
|
||||
asynSuccess (depending on whether `forcedPoll was called`).
|
||||
*/
|
||||
virtual asynStatus poll(bool *moving);
|
||||
|
||||
/**
|
||||
* @brief Perform some standardized operations before and after the concrete
|
||||
`doPoll` implementation.
|
||||
@@ -37,7 +83,7 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
|
||||
- The flags `motorStatusHome_`, `motorStatusHomed_` and
|
||||
`motorStatusAtHome_` are set to their idle values (0, 1 and 1 respectively)
|
||||
in the `poll()` method once the homing procedure is finished. See the
|
||||
in the `forcedPoll()` method once the homing procedure is finished. See the
|
||||
documentation of the `home()` method for more details.
|
||||
|
||||
- Run `callParamCallbacks()`
|
||||
@@ -47,9 +93,9 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
* @param moving Forwarded to `doPoll`.
|
||||
* @return asynStatus Forward the status of `doPoll`, unless one of
|
||||
the parameter library operation fails (in that case, returns the status of
|
||||
the failed operation.
|
||||
the failed operation).
|
||||
*/
|
||||
virtual asynStatus poll(bool *moving);
|
||||
asynStatus forcedPoll(bool *moving);
|
||||
|
||||
/**
|
||||
* @brief Implementation of the "proper", device-specific poll method. This
|
||||
@@ -133,7 +179,7 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
*
|
||||
* The flags `motorStatusHome_`, `motorStatusHomed_` and
|
||||
`motorStatusAtHome_` are set to their idle values (0, 1 and 1 respectively)
|
||||
in the `poll()` method once the homing procedure is finished.
|
||||
in the `forcedPoll())` method once the homing procedure is finished.
|
||||
*
|
||||
* @param minVelocity Forwarded to `doHome`.
|
||||
* @param maxVelocity Forwarded to `doHome`.
|
||||
@@ -297,10 +343,7 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
* @param offsetMovTimeout Offset (in seconds)
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus setOffsetMovTimeout(time_t offsetMovTimeout) {
|
||||
offsetMovTimeout_ = offsetMovTimeout;
|
||||
return asynSuccess;
|
||||
}
|
||||
virtual asynStatus setOffsetMovTimeout(time_t offsetMovTimeout);
|
||||
|
||||
/**
|
||||
* @brief Set the scaleMovTimeout. Also available in the IOC shell
|
||||
@@ -311,10 +354,7 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
* @param scaleMovTimeout Scaling factor (in seconds)
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus setScaleMovTimeout(time_t scaleMovTimeout) {
|
||||
scaleMovTimeout_ = scaleMovTimeout;
|
||||
return asynSuccess;
|
||||
}
|
||||
virtual asynStatus setScaleMovTimeout(time_t scaleMovTimeout);
|
||||
|
||||
/**
|
||||
* @brief Return the axis number of this axis
|
||||
@@ -362,34 +402,141 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
asynStatus assertConnected();
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the axis controller
|
||||
* @brief Return a pointer to the axis controller.
|
||||
*
|
||||
* This function should be overriden in derived classes using the `override`
|
||||
* keyword so the macros `getAxisParamChecked` and `setAxisParamChecked`
|
||||
* work correctly:
|
||||
*
|
||||
* ```
|
||||
* class mySpecialAxis : public sinqAxis {
|
||||
public:
|
||||
mySpecialController* getControllerMethod() override {
|
||||
return mySpecialControllerPtr;
|
||||
}
|
||||
};
|
||||
* ```
|
||||
*/
|
||||
sinqController *pController() { return pC_; };
|
||||
virtual sinqController *pController() { return pC_; };
|
||||
|
||||
protected:
|
||||
// Internal variables used in the movement timeout watchdog
|
||||
time_t expectedArrivalTime_;
|
||||
time_t offsetMovTimeout_;
|
||||
/**
|
||||
* @brief Returns true, if the axis was moving in the last poll cycle, and
|
||||
* false otherwise.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool wasMoving();
|
||||
|
||||
double scaleMovTimeout_;
|
||||
bool watchdogMovActive_;
|
||||
// Store the motor target position for the movement time calculation
|
||||
double targetPosition_;
|
||||
/**
|
||||
* @brief Override the wasMoving flag (normally, it is automatically updated
|
||||
* during each poll).
|
||||
*
|
||||
*/
|
||||
void setWasMoving(bool wasMoving);
|
||||
|
||||
bool wasMoving_;
|
||||
/**
|
||||
* @brief Read out the last received target position in engineering units.
|
||||
*
|
||||
* @return double
|
||||
*/
|
||||
double targetPosition();
|
||||
|
||||
/*
|
||||
Store the time since the last poll
|
||||
*/
|
||||
epicsTimeStamp lastPollTime_;
|
||||
/**
|
||||
* @brief Override the targetPosition value (normally, it is automatically
|
||||
* updated at every call of the move() method).
|
||||
*
|
||||
*/
|
||||
void setTargetPosition(double targetPosition);
|
||||
|
||||
private:
|
||||
// Ordering matters because pC_ is initialized before pSinqA_ in the
|
||||
// constructor
|
||||
sinqController *pC_;
|
||||
std::unique_ptr<sinqAxisImpl> pSinqA_;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Helper functions and definitions for the macro setAxisParamChecked
|
||||
|
||||
template <typename T> struct TypeTag {};
|
||||
|
||||
// Generic fallback - if the compiler tries to compile this function, it fails.
|
||||
template <typename A, typename C, typename T>
|
||||
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), T writeValue,
|
||||
const char *callerFunctionName, int lineNumber,
|
||||
TypeTag<void>) {
|
||||
static_assert(sizeof(T) == 0, "Unsupported type for setAxisParamImpl");
|
||||
return asynError;
|
||||
}
|
||||
|
||||
template <typename A, typename C>
|
||||
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), int writeValue,
|
||||
const char *callerFunctionName, int lineNumber,
|
||||
TypeTag<int>) {
|
||||
int indexValue = (controller->*func)();
|
||||
asynStatus status = axis->setIntegerParam(indexValue, writeValue);
|
||||
if (status != asynSuccess) {
|
||||
return controller->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
template <typename A, typename C>
|
||||
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), bool writeValue,
|
||||
const char *callerFunctionName, int lineNumber,
|
||||
TypeTag<bool>) {
|
||||
return setAxisParamImpl(axis, controller, indexName, func,
|
||||
static_cast<int>(writeValue), callerFunctionName,
|
||||
lineNumber, TypeTag<int>{});
|
||||
}
|
||||
|
||||
template <typename A, typename C>
|
||||
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), double writeValue,
|
||||
const char *callerFunctionName, int lineNumber,
|
||||
TypeTag<double>) {
|
||||
int indexValue = (controller->*func)();
|
||||
asynStatus status = axis->setDoubleParam(indexValue, writeValue);
|
||||
if (status != asynSuccess) {
|
||||
return controller->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
template <typename A, typename C>
|
||||
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), char *writeValue,
|
||||
const char *callerFunctionName, int lineNumber,
|
||||
TypeTag<char *>) {
|
||||
int indexValue = (controller->*func)();
|
||||
asynStatus status = axis->setStringParam(indexValue, writeValue);
|
||||
if (status != asynSuccess) {
|
||||
return controller->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
template <typename A, typename C>
|
||||
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), const char *writeValue,
|
||||
const char *callerFunctionName, int lineNumber,
|
||||
TypeTag<const char *>) {
|
||||
int indexValue = (controller->*func)();
|
||||
asynStatus status = axis->setStringParam(indexValue, writeValue);
|
||||
if (status != asynSuccess) {
|
||||
return controller->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function to set an integer / double / string parameter for an
|
||||
* axis in the paramLib
|
||||
@@ -397,8 +544,11 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
* This function should not be used directly, but rather through its macro
|
||||
* variant `setAxisParamChecked`.
|
||||
*
|
||||
* @tparam A
|
||||
* @tparam C
|
||||
* @tparam T
|
||||
* @param axis
|
||||
* @param controller
|
||||
* @param indexName
|
||||
* @param func
|
||||
* @param writeValue
|
||||
@@ -406,10 +556,13 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
* @param lineNumber
|
||||
* @return asynStatus
|
||||
*/
|
||||
template <typename T>
|
||||
asynStatus setAxisParam(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), T writeValue,
|
||||
const char *callerFunctionName, int lineNumber);
|
||||
template <typename A, typename C, typename T>
|
||||
asynStatus setAxisParam(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), T writeValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
return setAxisParamImpl(axis, controller, indexName, func, writeValue,
|
||||
callerFunctionName, lineNumber, TypeTag<T>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Macro to set an paramLib parameter and error checking the return value
|
||||
@@ -425,10 +578,10 @@ asynStatus setAxisParam(sinqAxis *axis, const char *indexName,
|
||||
* expands into the following code:
|
||||
* ```
|
||||
* {
|
||||
* int indexValue = axis->pController()->motorStatusProblem_();
|
||||
* int indexValue = controller->motorStatusProblem_();
|
||||
* asynStatus status = axis->setIntegerParam(indexValue, writeValue);
|
||||
* if (status != asynSuccess) {
|
||||
* return axis->pController()->paramLibAccessFailed(
|
||||
* return controller->paramLibAccessFailed(
|
||||
* status, "motorStatusProblem_", axis->axisNo(), __PRETTY_FUNCTION__,
|
||||
* __LINE__);
|
||||
* }
|
||||
@@ -437,23 +590,116 @@ asynStatus setAxisParam(sinqAxis *axis, const char *indexName,
|
||||
* ```
|
||||
* =============================================================================
|
||||
*/
|
||||
#define setAxisParamChecked(axis, indexGetterFunction, writeValue) \
|
||||
{ \
|
||||
asynStatus setStatus = setAxisParam( \
|
||||
axis, #indexGetterFunction, \
|
||||
&std::remove_pointer< \
|
||||
decltype(axis->pController())>::type::indexGetterFunction, \
|
||||
writeValue, __PRETTY_FUNCTION__, __LINE__); \
|
||||
if (setStatus != asynSuccess) { \
|
||||
#define setAxisParamChecked(axis, indexSetterFunction, writeValue) \
|
||||
do { \
|
||||
auto *ctrlPtr = (axis)->pController(); \
|
||||
using ControllerType = \
|
||||
typename std::remove_pointer<decltype(ctrlPtr)>::type; \
|
||||
asynStatus setStatus = \
|
||||
setAxisParam(axis, ctrlPtr, #indexSetterFunction, \
|
||||
static_cast<int (ControllerType::*)()>( \
|
||||
&ControllerType::indexSetterFunction), \
|
||||
writeValue, __PRETTY_FUNCTION__, __LINE__); \
|
||||
if (setStatus != asynSuccess) \
|
||||
return setStatus; \
|
||||
} \
|
||||
}
|
||||
} while (0)
|
||||
|
||||
// =============================================================================
|
||||
// Helper functions and definitions for the macro getAxisParamChecked
|
||||
|
||||
// Generic fallback - if the compiler tries to compile this function, it fails.
|
||||
template <typename A, typename C, typename T>
|
||||
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), T *readValue,
|
||||
const char *callerFunctionName, int lineNumber,
|
||||
size_t msgSize, TypeTag<void>) {
|
||||
static_assert(
|
||||
sizeof(T) == 0,
|
||||
"no specialization of getAxisParam exists for the given type");
|
||||
return asynError;
|
||||
}
|
||||
|
||||
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>) {
|
||||
int indexValue = (controller->*func)();
|
||||
asynStatus status =
|
||||
controller->getIntegerParam(axis->axisNo(), indexValue, readValue);
|
||||
if (status != asynSuccess) {
|
||||
return controller->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
template <typename A, typename C>
|
||||
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), bool *readValue,
|
||||
const char *callerFunctionName, int lineNumber,
|
||||
size_t msgSize, TypeTag<bool>) {
|
||||
int readValueInt = 0;
|
||||
asynStatus status = getAxisParamImpl(axis, controller, indexName, func,
|
||||
&readValueInt, callerFunctionName,
|
||||
lineNumber, msgSize, TypeTag<int>{});
|
||||
*readValue = readValueInt != 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
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>) {
|
||||
int indexValue = (controller->*func)();
|
||||
asynStatus status =
|
||||
controller->getDoubleParam(axis->axisNo(), indexValue, readValue);
|
||||
if (status != asynSuccess) {
|
||||
return controller->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
template <typename A, typename C>
|
||||
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), char *readValue,
|
||||
const char *callerFunctionName, int lineNumber,
|
||||
size_t msgSize, TypeTag<char>) {
|
||||
|
||||
int indexValue = (controller->*func)();
|
||||
asynStatus status = controller->getStringParam(axis->axisNo(), indexValue,
|
||||
msgSize, readValue);
|
||||
if (status != asynSuccess) {
|
||||
return controller->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
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>) {
|
||||
int indexValue = (controller->*func)();
|
||||
|
||||
// Convert the pointer to a reference, since getStringParam expects the
|
||||
// latter.
|
||||
std::string &rReadValue = *readValue;
|
||||
|
||||
asynStatus status =
|
||||
controller->getStringParam(axis->axisNo(), indexValue, rReadValue);
|
||||
if (status != asynSuccess) {
|
||||
return controller->paramLibAccessFailed(
|
||||
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function to set an integer / double / string parameter for an
|
||||
* @brief Helper function to get an integer / double / string parameter for an
|
||||
* axis in the paramLib
|
||||
*
|
||||
* This function should not be used directly, but rather through its macro
|
||||
@@ -461,6 +707,7 @@ asynStatus setAxisParam(sinqAxis *axis, const char *indexName,
|
||||
*
|
||||
* @tparam T
|
||||
* @param axis
|
||||
* @param controller
|
||||
* @param indexName
|
||||
* @param func
|
||||
* @param readValue
|
||||
@@ -471,17 +718,48 @@ asynStatus setAxisParam(sinqAxis *axis, const char *indexName,
|
||||
* to.
|
||||
* @return asynStatus
|
||||
*/
|
||||
template <typename T>
|
||||
asynStatus getAxisParam(sinqAxis *axis, const char *indexName,
|
||||
int (sinqController::*func)(), T *readValue,
|
||||
const char *callerFunctionName, int lineNumber);
|
||||
template <typename A, typename C, typename T>
|
||||
asynStatus getAxisParam(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), T *readValue,
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
return getAxisParamImpl(axis, controller, indexName, func, readValue,
|
||||
callerFunctionName, lineNumber,
|
||||
controller->msgSize(), TypeTag<T>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function to get a string parameter for an
|
||||
* axis in the paramLib into a char array
|
||||
*
|
||||
* This function should not be used directly, but rather through its macro
|
||||
* variant `getAxisParamChecked`. It is a specialized variant of the general
|
||||
* getAxisParam defined above for char arrays.
|
||||
*
|
||||
* @tparam A
|
||||
* @tparam C
|
||||
* @tparam N
|
||||
* @param axis
|
||||
* @param controller
|
||||
* @param indexName
|
||||
* @param func
|
||||
* @param callerFunctionName
|
||||
* @param lineNumber
|
||||
* @return asynStatus
|
||||
*/
|
||||
template <typename A, typename C, size_t N>
|
||||
asynStatus getAxisParam(A *axis, C *controller, const char *indexName,
|
||||
int (C::*func)(), char (*readValue)[N],
|
||||
const char *callerFunctionName, int lineNumber) {
|
||||
return getAxisParamImpl(axis, controller, indexName, func, *readValue,
|
||||
callerFunctionName, lineNumber, N, TypeTag<char>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Macro to get an paramLib parameter and error checking the return value
|
||||
*
|
||||
* This macro is a wrapper around `getIntegerParam` / `getDoubleParam` /
|
||||
* `getStringParam` which checks if the operation was successfull. If it wasn't,
|
||||
* it returns by calling the paramLibAccessFailed function.
|
||||
* it returns by calling the paramLibAccessFailed function. In order
|
||||
*
|
||||
* For example, the following input:
|
||||
* ```
|
||||
@@ -490,10 +768,10 @@ asynStatus getAxisParam(sinqAxis *axis, const char *indexName,
|
||||
* expands into the following code:
|
||||
* ```
|
||||
* {
|
||||
* int indexValue = axis->pController()->motorStatusProblem_();
|
||||
* asynStatus status = axis->pController()->getIntegerParam(axis->axisNo(),
|
||||
* int indexValue = controller->motorStatusProblem_();
|
||||
* asynStatus status = controller->getIntegerParam(axis->axisNo(),
|
||||
* indexValue, readValue); if (status != asynSuccess) { return
|
||||
* axis->pController()->paramLibAccessFailed( status, "motorStatusProblem_",
|
||||
* controller->paramLibAccessFailed( status, "motorStatusProblem_",
|
||||
* axis->axisNo(), __PRETTY_FUNCTION__,
|
||||
* __LINE__);
|
||||
* }
|
||||
@@ -503,15 +781,17 @@ asynStatus getAxisParam(sinqAxis *axis, const char *indexName,
|
||||
* =============================================================================
|
||||
*/
|
||||
#define getAxisParamChecked(axis, indexGetterFunction, readValue) \
|
||||
{ \
|
||||
asynStatus getStatus = getAxisParam( \
|
||||
axis, #indexGetterFunction, \
|
||||
&std::remove_pointer< \
|
||||
decltype(axis->pController())>::type::indexGetterFunction, \
|
||||
readValue, __PRETTY_FUNCTION__, __LINE__); \
|
||||
if (getStatus != asynSuccess) { \
|
||||
do { \
|
||||
auto *ctrlPtr = (axis)->pController(); \
|
||||
using ControllerType = \
|
||||
typename std::remove_pointer<decltype(ctrlPtr)>::type; \
|
||||
asynStatus getStatus = \
|
||||
getAxisParam(axis, ctrlPtr, #indexGetterFunction, \
|
||||
static_cast<int (ControllerType::*)()>( \
|
||||
&ControllerType::indexGetterFunction), \
|
||||
readValue, __PRETTY_FUNCTION__, __LINE__); \
|
||||
if (getStatus != asynSuccess) \
|
||||
return getStatus; \
|
||||
} \
|
||||
}
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,12 +1,23 @@
|
||||
// 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 "sinqAxis.h"
|
||||
#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>
|
||||
|
||||
/*
|
||||
@@ -34,6 +45,66 @@ void sinqController::epicsInithookFunction(initHookState iState) {
|
||||
}
|
||||
}
|
||||
|
||||
struct sinqControllerImpl {
|
||||
// Number of fast polls which still need to be performed before adaptive
|
||||
// polling is active again.
|
||||
int outstandingForcedFastPolls;
|
||||
|
||||
// Number of polls forced by wakeupPoller which are still
|
||||
|
||||
// Pointer to the port user which is specified by the char array
|
||||
// `ipPortConfigName` in the constructor
|
||||
asynUser *pasynOctetSyncIOipPort;
|
||||
|
||||
// Message print control
|
||||
msgPrintControl msgPrintC;
|
||||
|
||||
// Internal variables used in the communication timeout frequency watchdog
|
||||
time_t comTimeoutWindow; // Size of the time window
|
||||
size_t maxNumberTimeouts; // Maximum acceptable number of events within the
|
||||
// time window
|
||||
|
||||
// Deque holding the timestamps of the individual events
|
||||
std::deque<time_t> timeoutEvents;
|
||||
|
||||
// Communicate a timeout to the user after it has happened this many times
|
||||
// in a row
|
||||
int maxSubsequentTimeouts;
|
||||
bool maxSubsequentTimeoutsExceeded;
|
||||
|
||||
/*
|
||||
These integers are indices to paramLib entries and are populated when the
|
||||
parameters are created. See the documentation in db/sinqMotor.db.
|
||||
*/
|
||||
int motorMessageText;
|
||||
int motorReset;
|
||||
int motorEnable;
|
||||
int motorEnableRBV;
|
||||
int motorCanDisable;
|
||||
int motorEnableMovWatchdog;
|
||||
int motorCanSetSpeed;
|
||||
int motorLimitsOffset;
|
||||
int motorForceStop;
|
||||
int motorConnected;
|
||||
/*
|
||||
These parameters are here to write values from the hardware to the EPICS
|
||||
motor record. Using motorHighLimit_ / motorLowLimit_ does not work:
|
||||
https://epics.anl.gov/tech-talk/2023/msg00576.php. Therefore, some
|
||||
additional records are introduced which read from these parameters and write
|
||||
into the motor record.
|
||||
*/
|
||||
int motorVeloFromDriver;
|
||||
int motorVbasFromDriver;
|
||||
int motorVmaxFromDriver;
|
||||
int motorAcclFromDriver;
|
||||
int motorHighLimitFromDriver;
|
||||
int motorLowLimitFromDriver;
|
||||
int motorPositionDeadband;
|
||||
int adaptivePolling;
|
||||
int encoderType;
|
||||
};
|
||||
#define NUM_SINQMOTOR_DRIVER_PARAMS 18
|
||||
|
||||
sinqController::sinqController(const char *portName,
|
||||
const char *ipPortConfigName, int numAxes,
|
||||
double movingPollPeriod, double idlePollPeriod,
|
||||
@@ -50,28 +121,38 @@ sinqController::sinqController(const char *portName,
|
||||
ASYN_CANBLOCK | ASYN_MULTIDEVICE,
|
||||
1, // autoconnect
|
||||
0, 0), // Default priority and stack size
|
||||
msgPrintControl_() {
|
||||
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;
|
||||
|
||||
// Handle to the asynUser of the IP port asyn driver
|
||||
pasynOctetSyncIOipPort_ = nullptr;
|
||||
|
||||
// Initial values for the average timeout mechanism, can be overwritten
|
||||
// later by a FFI function
|
||||
comTimeoutWindow_ = 3600; // seconds
|
||||
|
||||
// Number of timeouts which may occur before an error is forwarded to the
|
||||
// user
|
||||
maxNumberTimeouts_ = 60;
|
||||
|
||||
// Queue holding the timeout event timestamps
|
||||
timeoutEvents_ = {};
|
||||
|
||||
// Inform the user after 10 timeouts in a row (default value)
|
||||
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;
|
||||
@@ -83,9 +164,9 @@ sinqController::sinqController(const char *portName,
|
||||
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, &pasynOctetSyncIOipPort_,
|
||||
NULL);
|
||||
if (status != asynSuccess || pasynOctetSyncIOipPort_ == nullptr) {
|
||||
pasynOctetSyncIO->connect(ipPortConfigName, 0,
|
||||
&pSinqC_->pasynOctetSyncIOipPort, NULL);
|
||||
if (status != asynSuccess || pSinqC_->pasynOctetSyncIOipPort == nullptr) {
|
||||
errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot "
|
||||
"connect to MCU controller).\n"
|
||||
"Terminating IOC",
|
||||
@@ -98,8 +179,8 @@ sinqController::sinqController(const char *portName,
|
||||
// MOTOR_MESSAGE_TEXT corresponds to the PV definition inside sinqMotor.db.
|
||||
// This text is used to forward status messages to NICOS and in turn to the
|
||||
// user.
|
||||
status =
|
||||
createParam("MOTOR_MESSAGE_TEXT", asynParamOctet, &motorMessageText_);
|
||||
status = createParam("MOTOR_MESSAGE_TEXT", asynParamOctet,
|
||||
&pSinqC_->motorMessageText);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -109,7 +190,7 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_ENABLE", asynParamInt32, &motorEnable_);
|
||||
status = createParam("MOTOR_ENABLE", asynParamInt32, &pSinqC_->motorEnable);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -119,7 +200,7 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_RESET", asynParamInt32, &motorReset_);
|
||||
status = createParam("MOTOR_RESET", asynParamInt32, &pSinqC_->motorReset);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -129,7 +210,8 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_ENABLE_RBV", asynParamInt32, &motorEnableRBV_);
|
||||
status = createParam("MOTOR_ENABLE_RBV", asynParamInt32,
|
||||
&pSinqC_->motorEnableRBV);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -139,8 +221,8 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status =
|
||||
createParam("MOTOR_CAN_DISABLE", asynParamInt32, &motorCanDisable_);
|
||||
status = createParam("MOTOR_CAN_DISABLE", asynParamInt32,
|
||||
&pSinqC_->motorCanDisable);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -150,8 +232,8 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status =
|
||||
createParam("MOTOR_CAN_SET_SPEED", asynParamInt32, &motorCanSetSpeed_);
|
||||
status = createParam("MOTOR_CAN_SET_SPEED", asynParamInt32,
|
||||
&pSinqC_->motorCanSetSpeed);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -162,7 +244,7 @@ sinqController::sinqController(const char *portName,
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_LIMITS_OFFSET", asynParamFloat64,
|
||||
&motorLimitsOffset_);
|
||||
&pSinqC_->motorLimitsOffset);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -172,7 +254,8 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_CONNECTED", asynParamInt32, &motorConnected_);
|
||||
status = createParam("MOTOR_CONNECTED", asynParamInt32,
|
||||
&pSinqC_->motorConnected);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -188,7 +271,7 @@ sinqController::sinqController(const char *portName,
|
||||
the declaration of motorHighLimitFromDriver_.
|
||||
*/
|
||||
status = createParam("MOTOR_HIGH_LIMIT_FROM_DRIVER", asynParamFloat64,
|
||||
&motorHighLimitFromDriver_);
|
||||
&pSinqC_->motorHighLimitFromDriver);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -199,7 +282,7 @@ sinqController::sinqController(const char *portName,
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_LOW_LIMIT_FROM_DRIVER", asynParamFloat64,
|
||||
&motorLowLimitFromDriver_);
|
||||
&pSinqC_->motorLowLimitFromDriver);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -209,8 +292,19 @@ 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,
|
||||
&motorEnableMovWatchdog_);
|
||||
&pSinqC_->motorEnableMovWatchdog);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -221,7 +315,7 @@ sinqController::sinqController(const char *portName,
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_VELO_FROM_DRIVER", asynParamFloat64,
|
||||
&motorVeloFromDriver_);
|
||||
&pSinqC_->motorVeloFromDriver);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -232,7 +326,7 @@ sinqController::sinqController(const char *portName,
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_VBAS_FROM_DRIVER", asynParamFloat64,
|
||||
&motorVbasFromDriver_);
|
||||
&pSinqC_->motorVbasFromDriver);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -243,7 +337,7 @@ sinqController::sinqController(const char *portName,
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_VMAX_FROM_DRIVER", asynParamFloat64,
|
||||
&motorVmaxFromDriver_);
|
||||
&pSinqC_->motorVmaxFromDriver);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -254,7 +348,7 @@ sinqController::sinqController(const char *portName,
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_ACCL_FROM_DRIVER", asynParamFloat64,
|
||||
&motorAcclFromDriver_);
|
||||
&pSinqC_->motorAcclFromDriver);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -264,7 +358,8 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("ADAPTIVE_POLLING", asynParamInt32, &adaptivePolling_);
|
||||
status = createParam("ADAPTIVE_POLLING", asynParamInt32,
|
||||
&pSinqC_->adaptivePolling);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -274,7 +369,7 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("ENCODER_TYPE", asynParamOctet, &encoderType_);
|
||||
status = createParam("ENCODER_TYPE", asynParamOctet, &pSinqC_->encoderType);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -284,7 +379,8 @@ sinqController::sinqController(const char *portName,
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
status = createParam("MOTOR_FORCE_STOP", asynParamInt32, &motorForceStop_);
|
||||
status = createParam("MOTOR_FORCE_STOP", asynParamInt32,
|
||||
&pSinqC_->motorForceStop);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
|
||||
@@ -343,19 +439,21 @@ 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;
|
||||
}
|
||||
|
||||
// Handle custom PVs
|
||||
if (function == motorEnable_) {
|
||||
if (function == motorEnable()) {
|
||||
return axis->enable(value != 0);
|
||||
} else if (function == motorReset_) {
|
||||
} else if (function == motorReset()) {
|
||||
return axis->reset();
|
||||
} else if (function == motorForceStop_) {
|
||||
} else if (function == motorForceStop()) {
|
||||
return axis->stop(0.0);
|
||||
} else {
|
||||
return asynMotorController::writeInt32(pasynUser, value);
|
||||
@@ -366,17 +464,19 @@ 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;
|
||||
}
|
||||
|
||||
if (pasynUser->reason == motorEnableRBV_) {
|
||||
if (pasynUser->reason == motorEnableRBV()) {
|
||||
getAxisParamChecked(axis, motorEnableRBV, value);
|
||||
return asynSuccess;
|
||||
} else if (pasynUser->reason == motorCanDisable_) {
|
||||
} else if (pasynUser->reason == motorCanDisable()) {
|
||||
getAxisParamChecked(axis, motorCanDisable, value);
|
||||
return asynSuccess;
|
||||
} else {
|
||||
@@ -389,7 +489,7 @@ asynStatus sinqController::couldNotParseResponse(const char *command,
|
||||
int axisNo,
|
||||
const char *functionName,
|
||||
int line) {
|
||||
asynPrint(pasynOctetSyncIOipPort_, ASYN_TRACE_ERROR,
|
||||
asynPrint(pasynOctetSyncIOipPort(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nCould not interpret "
|
||||
"response \"%s\" for command \"%s\".\n",
|
||||
portName, axisNo, functionName, line, response, command);
|
||||
@@ -399,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;
|
||||
}
|
||||
|
||||
@@ -418,7 +518,7 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
|
||||
int line) {
|
||||
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pasynOctetSyncIOipPort_, ASYN_TRACE_ERROR,
|
||||
asynPrint(pasynOctetSyncIOipPort(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\n Accessing the "
|
||||
"parameter library failed for parameter %s with error %s.\n",
|
||||
portName, axisNo, functionName, line, parameter,
|
||||
@@ -440,40 +540,42 @@ asynStatus sinqController::checkComTimeoutWatchdog(int axisNo,
|
||||
asynStatus paramLibStatus = asynSuccess;
|
||||
|
||||
// Add a new timeout event to the queue
|
||||
timeoutEvents_.push_back(time(NULL));
|
||||
pSinqC_->timeoutEvents.push_back(time(NULL));
|
||||
|
||||
// Remove every event which is older than the time window from the deque
|
||||
while (1) {
|
||||
if (timeoutEvents_.empty()) {
|
||||
if (pSinqC_->timeoutEvents.empty()) {
|
||||
break;
|
||||
}
|
||||
if (timeoutEvents_[0] + comTimeoutWindow_ <= time(NULL)) {
|
||||
timeoutEvents_.pop_front();
|
||||
if (pSinqC_->timeoutEvents[0] + pSinqC_->comTimeoutWindow <=
|
||||
time(NULL)) {
|
||||
pSinqC_->timeoutEvents.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the maximum allowed number of events has been exceeded
|
||||
bool wantToPrint = timeoutEvents_.size() > maxNumberTimeouts_;
|
||||
bool wantToPrint =
|
||||
pSinqC_->timeoutEvents.size() > pSinqC_->maxNumberTimeouts;
|
||||
|
||||
if (msgPrintControl_.shouldBePrinted(portName, axisNo, __PRETTY_FUNCTION__,
|
||||
__LINE__, wantToPrint,
|
||||
pasynUserSelf)) {
|
||||
if (pSinqC_->msgPrintC.shouldBePrinted(portName, axisNo,
|
||||
__PRETTY_FUNCTION__, __LINE__,
|
||||
wantToPrint, pasynUserSelf)) {
|
||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nMore than %ld "
|
||||
"communication timeouts in %ld "
|
||||
"seconds.%s\n",
|
||||
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
|
||||
maxNumberTimeouts_, comTimeoutWindow_,
|
||||
msgPrintControl_.getSuffix());
|
||||
pSinqC_->maxNumberTimeouts, pSinqC_->comTimeoutWindow,
|
||||
pSinqC_->msgPrintC.getSuffix());
|
||||
}
|
||||
|
||||
if (wantToPrint) {
|
||||
snprintf(motorMessage, motorMessageSize,
|
||||
"More than %ld communication timeouts in %ld seconds. Please "
|
||||
"call the support.",
|
||||
maxNumberTimeouts_, comTimeoutWindow_);
|
||||
pSinqC_->maxNumberTimeouts, pSinqC_->comTimeoutWindow);
|
||||
|
||||
paramLibStatus = setIntegerParam(motorStatusCommsError_, 1);
|
||||
if (paramLibStatus != asynSuccess) {
|
||||
@@ -505,8 +607,8 @@ asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo, int axisNo,
|
||||
size_t motorMessageSize) {
|
||||
asynStatus paramLibStatus = asynSuccess;
|
||||
|
||||
if (timeoutNo >= maxSubsequentTimeouts_) {
|
||||
if (!maxSubsequentTimeoutsExceeded_) {
|
||||
if (timeoutNo >= pSinqC_->maxSubsequentTimeouts) {
|
||||
if (!pSinqC_->maxSubsequentTimeoutsExceeded) {
|
||||
snprintf(motorMessage, motorMessageSize,
|
||||
"Communication timeout between IOC and motor controller. "
|
||||
"Trying to reconnect ...");
|
||||
@@ -516,7 +618,7 @@ asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo, int axisNo,
|
||||
"subsequent communication timeouts. Check whether the "
|
||||
"controller is still running and connected to the network.\n",
|
||||
this->portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
|
||||
maxSubsequentTimeouts_);
|
||||
pSinqC_->maxSubsequentTimeouts);
|
||||
|
||||
paramLibStatus = setIntegerParam(motorStatusCommsError_, 1);
|
||||
if (paramLibStatus != asynSuccess) {
|
||||
@@ -524,12 +626,12 @@ asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo, int axisNo,
|
||||
"motorStatusCommsError_", axisNo,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
maxSubsequentTimeoutsExceeded_ = true;
|
||||
pSinqC_->maxSubsequentTimeoutsExceeded = true;
|
||||
}
|
||||
|
||||
return asynError;
|
||||
} else {
|
||||
maxSubsequentTimeoutsExceeded_ = false;
|
||||
pSinqC_->maxSubsequentTimeoutsExceeded = false;
|
||||
motorMessage[0] = '\0';
|
||||
return asynSuccess;
|
||||
}
|
||||
@@ -551,8 +653,8 @@ asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo,
|
||||
asynStatus sinqController::poll() {
|
||||
// Decrement the number of outstanding forced fast polls, if they are not
|
||||
// zero
|
||||
if (outstandingForcedFastPolls_ > 0) {
|
||||
outstandingForcedFastPolls_--;
|
||||
if (pSinqC_->outstandingForcedFastPolls > 0) {
|
||||
pSinqC_->outstandingForcedFastPolls--;
|
||||
}
|
||||
return asynMotorController::poll();
|
||||
}
|
||||
@@ -560,10 +662,89 @@ asynStatus sinqController::poll() {
|
||||
asynStatus sinqController::wakeupPoller() {
|
||||
// + 1 since outstandingForcedFastPolls_ is reduced once at the start of
|
||||
// a poll cycle
|
||||
outstandingForcedFastPolls_ = forcedFastPolls_ + 1;
|
||||
pSinqC_->outstandingForcedFastPolls = forcedFastPolls_ + 1;
|
||||
return asynMotorController::wakeupPoller();
|
||||
}
|
||||
|
||||
asynStatus sinqController::setMaxSubsequentTimeouts(int maxSubsequentTimeouts) {
|
||||
pSinqC_->maxSubsequentTimeouts = maxSubsequentTimeouts;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
bool sinqController::maxSubsequentTimeoutsExceeded() {
|
||||
return pSinqC_->maxSubsequentTimeoutsExceeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a reference to the map used to control the maximum number of
|
||||
* message repetitions. See the documentation of `printRepetitionWatchdog`
|
||||
* in msgPrintControl.h for details.
|
||||
*/
|
||||
msgPrintControl &sinqController::getMsgPrintControl() {
|
||||
return pSinqC_->msgPrintC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the number of outstanding forced fast polls currently
|
||||
* specified
|
||||
*
|
||||
*/
|
||||
int sinqController::outstandingForcedFastPolls() {
|
||||
return pSinqC_->outstandingForcedFastPolls;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the low-level octet (string) IP Port
|
||||
*
|
||||
* @return asynUser*
|
||||
*/
|
||||
asynUser *sinqController::pasynOctetSyncIOipPort() {
|
||||
return pSinqC_->pasynOctetSyncIOipPort;
|
||||
}
|
||||
|
||||
asynStatus sinqController::setThresholdComTimeout(time_t comTimeoutWindow,
|
||||
size_t maxNumberTimeouts) {
|
||||
pSinqC_->comTimeoutWindow = comTimeoutWindow;
|
||||
pSinqC_->maxNumberTimeouts = maxNumberTimeouts;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
int sinqController::motorMessageText() { return pSinqC_->motorMessageText; }
|
||||
int sinqController::motorReset() { return pSinqC_->motorReset; }
|
||||
int sinqController::motorEnable() { return pSinqC_->motorEnable; }
|
||||
int sinqController::motorEnableRBV() { return pSinqC_->motorEnableRBV; }
|
||||
int sinqController::motorCanDisable() { return pSinqC_->motorCanDisable; }
|
||||
int sinqController::motorEnableMovWatchdog() {
|
||||
return pSinqC_->motorEnableMovWatchdog;
|
||||
}
|
||||
int sinqController::motorCanSetSpeed() { return pSinqC_->motorCanSetSpeed; }
|
||||
int sinqController::motorLimitsOffset() { return pSinqC_->motorLimitsOffset; }
|
||||
int sinqController::motorForceStop() { return pSinqC_->motorForceStop; }
|
||||
int sinqController::motorConnected() { return pSinqC_->motorConnected; }
|
||||
int sinqController::motorVeloFromDriver() {
|
||||
return pSinqC_->motorVeloFromDriver;
|
||||
}
|
||||
int sinqController::motorVbasFromDriver() {
|
||||
return pSinqC_->motorVbasFromDriver;
|
||||
}
|
||||
int sinqController::motorVmaxFromDriver() {
|
||||
return pSinqC_->motorVmaxFromDriver;
|
||||
}
|
||||
int sinqController::motorAcclFromDriver() {
|
||||
return pSinqC_->motorAcclFromDriver;
|
||||
}
|
||||
int sinqController::motorHighLimitFromDriver() {
|
||||
return pSinqC_->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; }
|
||||
|
||||
// Static pointers (valid for the entire lifetime of the IOC). The number behind
|
||||
// the strings gives the integer number of each variant (see also method
|
||||
// stringifyAsynStatus)
|
||||
@@ -671,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);
|
||||
@@ -732,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);
|
||||
}
|
||||
@@ -786,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);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,19 @@ 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 <deque>
|
||||
#include <initHooks.h>
|
||||
#include <unordered_map>
|
||||
#include <macros.h>
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <memory>
|
||||
|
||||
#define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER"
|
||||
#define motorMessageTextString "MOTOR_MESSAGE_TEXT"
|
||||
@@ -21,7 +29,9 @@ Stefan Mathis, November 2024
|
||||
#define AbsoluteEncoder "absolute"
|
||||
#define NoEncoder "none"
|
||||
|
||||
class epicsShareClass sinqController : public asynMotorController {
|
||||
struct HIDDEN sinqControllerImpl;
|
||||
|
||||
class HIDDEN sinqController : public asynMotorController {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new sinqController object
|
||||
@@ -56,6 +66,13 @@ class epicsShareClass 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
|
||||
*
|
||||
@@ -175,11 +192,7 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus setThresholdComTimeout(time_t comTimeoutWindow,
|
||||
size_t maxNumberTimeouts) {
|
||||
comTimeoutWindow_ = comTimeoutWindow;
|
||||
maxNumberTimeouts_ = maxNumberTimeouts;
|
||||
return asynSuccess;
|
||||
}
|
||||
size_t maxNumberTimeouts);
|
||||
|
||||
/**
|
||||
* @brief Inform the user, if the number of timeouts exceeds the threshold
|
||||
@@ -211,17 +224,23 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
* @param maxSubsequentTimeouts
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus setMaxSubsequentTimeouts(int maxSubsequentTimeouts) {
|
||||
maxSubsequentTimeouts_ = maxSubsequentTimeouts;
|
||||
return asynSuccess;
|
||||
}
|
||||
asynStatus setMaxSubsequentTimeouts(int maxSubsequentTimeouts);
|
||||
|
||||
/**
|
||||
* @brief If true, the maximum number of subsequent communication timeouts
|
||||
* set in `setMaxSubsequentTimeouts` has been exceeded
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool maxSubsequentTimeoutsExceeded();
|
||||
|
||||
/**
|
||||
* @brief Get a reference to the map used to control the maximum number of
|
||||
* message repetitions. See the documentation of `printRepetitionWatchdog`
|
||||
* in msgPrintControl.h for details.
|
||||
*/
|
||||
msgPrintControl &getMsgPrintControl() { return msgPrintControl_; }
|
||||
msgPrintControl &getMsgPrintControl();
|
||||
|
||||
/**
|
||||
* @brief Get the axis object
|
||||
@@ -293,25 +312,27 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
int motorRecDirection() { return motorRecDirection_; }
|
||||
int motorRecOffset() { return motorRecOffset_; }
|
||||
|
||||
// Accessors for additional PVs defined in sinqController
|
||||
int motorMessageText() { return motorMessageText_; }
|
||||
int motorReset() { return motorReset_; }
|
||||
int motorEnable() { return motorEnable_; }
|
||||
int motorEnableRBV() { return motorEnableRBV_; }
|
||||
int motorCanDisable() { return motorCanDisable_; }
|
||||
int motorEnableMovWatchdog() { return motorEnableMovWatchdog_; }
|
||||
int motorCanSetSpeed() { return motorCanSetSpeed_; }
|
||||
int motorLimitsOffset() { return motorLimitsOffset_; }
|
||||
int motorForceStop() { return motorForceStop_; }
|
||||
int motorConnected() { return motorConnected_; }
|
||||
int motorVeloFromDriver() { return motorVeloFromDriver_; }
|
||||
int motorVbasFromDriver() { return motorVbasFromDriver_; }
|
||||
int motorVmaxFromDriver() { return motorVmaxFromDriver_; }
|
||||
int motorAcclFromDriver() { return motorAcclFromDriver_; }
|
||||
int motorHighLimitFromDriver() { return motorHighLimitFromDriver_; }
|
||||
int motorLowLimitFromDriver() { return motorLowLimitFromDriver_; }
|
||||
int adaptivePolling() { return adaptivePolling_; }
|
||||
int encoderType() { return encoderType_; }
|
||||
// Accessors for additional PVs defined in sinqController (which are hidden
|
||||
// in pSinqC_)
|
||||
int motorMessageText();
|
||||
int motorReset();
|
||||
int motorEnable();
|
||||
int motorEnableRBV();
|
||||
int motorCanDisable();
|
||||
int motorEnableMovWatchdog();
|
||||
int motorCanSetSpeed();
|
||||
int motorLimitsOffset();
|
||||
int motorForceStop();
|
||||
int motorConnected();
|
||||
int motorVeloFromDriver();
|
||||
int motorVbasFromDriver();
|
||||
int motorVmaxFromDriver();
|
||||
int motorAcclFromDriver();
|
||||
int motorHighLimitFromDriver();
|
||||
int motorLowLimitFromDriver();
|
||||
int motorPositionDeadband();
|
||||
int adaptivePolling();
|
||||
int encoderType();
|
||||
|
||||
// Additional members
|
||||
int numAxes() { return numAxes_; }
|
||||
@@ -330,7 +351,7 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
*
|
||||
* @return asynUser*
|
||||
*/
|
||||
asynUser *pasynOctetSyncIOipPort() { return pasynOctetSyncIOipPort_; }
|
||||
asynUser *pasynOctetSyncIOipPort();
|
||||
|
||||
/**
|
||||
* @brief Overloaded version of `asynController::poll` which decreases
|
||||
@@ -374,76 +395,26 @@ class epicsShareClass sinqController : public asynMotorController {
|
||||
* specified
|
||||
*
|
||||
*/
|
||||
int outstandingForcedFastPolls() { return outstandingForcedFastPolls_; }
|
||||
int outstandingForcedFastPolls();
|
||||
|
||||
// Maximum error message buffer size. This is an empirical value which must
|
||||
// be large enough to avoid overflows for all commands to the device /
|
||||
// responses from it.
|
||||
/**
|
||||
* @brief Return the maximum error message buffer size
|
||||
*
|
||||
* This is an empirical value which must be large enough to avoid overflows
|
||||
* for all commands to the device / responses from it.
|
||||
*
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t msgSize() { return MAXBUF_; }
|
||||
|
||||
// Maximum message size
|
||||
static const uint32_t MAXBUF_ = 200;
|
||||
|
||||
// =========================================================================
|
||||
|
||||
protected:
|
||||
// Number of fast polls which still need to be performed before adaptive
|
||||
// polling is active again.
|
||||
int outstandingForcedFastPolls_;
|
||||
|
||||
// Number of polls forced by wakeupPoller which are still
|
||||
|
||||
// Pointer to the port user which is specified by the char array
|
||||
// `ipPortConfigName` in the constructor
|
||||
asynUser *pasynOctetSyncIOipPort_;
|
||||
double movingPollPeriod_;
|
||||
double idlePollPeriod_;
|
||||
msgPrintControl msgPrintControl_;
|
||||
|
||||
// Internal variables used in the communication timeout frequency watchdog
|
||||
time_t comTimeoutWindow_; // Size of the time window
|
||||
size_t maxNumberTimeouts_; // Maximum acceptable number of events within the
|
||||
// time window
|
||||
std::deque<time_t>
|
||||
timeoutEvents_; // Deque holding the timestamps of the individual events
|
||||
|
||||
// Communicate a timeout to the user after it has happened this many times
|
||||
// in a row
|
||||
int maxSubsequentTimeouts_;
|
||||
bool maxSubsequentTimeoutsExceeded_;
|
||||
|
||||
/*
|
||||
See the documentation in db/sinqMotor.db for the following integers
|
||||
*/
|
||||
#define FIRST_SINQMOTOR_PARAM motorMessageText_
|
||||
int motorMessageText_;
|
||||
int motorReset_;
|
||||
int motorEnable_;
|
||||
int motorEnableRBV_;
|
||||
int motorCanDisable_;
|
||||
int motorEnableMovWatchdog_;
|
||||
int motorCanSetSpeed_;
|
||||
int motorLimitsOffset_;
|
||||
int motorForceStop_;
|
||||
int motorConnected_;
|
||||
/*
|
||||
These parameters are here to write values from the hardware to the EPICS
|
||||
motor record. Using motorHighLimit_ / motorLowLimit_ does not work:
|
||||
https://epics.anl.gov/tech-talk/2023/msg00576.php. Therefore, some
|
||||
additional records are introduced which read from these parameters and write
|
||||
into the motor record.
|
||||
*/
|
||||
int motorVeloFromDriver_;
|
||||
int motorVbasFromDriver_;
|
||||
int motorVmaxFromDriver_;
|
||||
int motorAcclFromDriver_;
|
||||
int motorHighLimitFromDriver_;
|
||||
int motorLowLimitFromDriver_;
|
||||
int adaptivePolling_;
|
||||
int encoderType_;
|
||||
#define LAST_SINQMOTOR_PARAM encoderType_
|
||||
|
||||
private:
|
||||
std::unique_ptr<sinqControllerImpl> pSinqC_;
|
||||
static void epicsInithookFunction(initHookState iState);
|
||||
};
|
||||
#define NUM_SINQMOTOR_DRIVER_PARAMS \
|
||||
(&LAST_SINQMOTOR_PARAM - &FIRST_SINQMOTOR_PARAM + 1)
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user