Compare commits
110 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 | |||
| 275672aaef | |||
| 61c5ec749e | |||
| 1cf4b9ab25 | |||
| a0b674b26e | |||
| e088bfbbcb | |||
| 3c345e37da | |||
| b267200039 | |||
| 9bc90cff61 | |||
| 9792697d03 | |||
| 4d1c21fd74 | |||
| dbcfebc6de | |||
| b89fe41c6e | |||
| 5689402375 | |||
| 2a7934b8d6 | |||
| 3071e402b2 | |||
| dd0610fd99 | |||
| c7936191d9 | |||
| 3ec83b115e | |||
| bfda809257 | |||
| 76a91d4a2f | |||
| 228bcf7fd7 | |||
| db03ffea0e | |||
| 4c3254687d | |||
| eb94379efe | |||
| 1dd132c709 | |||
| 7729eceb28 | |||
| 828e9bc59c | |||
| f26d1bb612 | |||
| bed245b010 | |||
| ca7bede4b7 | |||
| d3307db987 | |||
| 591509bd43 | |||
| 5854d2c9d0 | |||
| f134a61649 | |||
| 49be84068f | |||
| e92a867189 | |||
| 8bc6109e2a | |||
| c06cf8e76c | |||
| f14ac66971 | |||
| b6e0f03a17 | |||
| 5946563372 | |||
| da96b4b973 | |||
| 61087d2e44 | |||
| 26754e608d | |||
| a866023957 | |||
| 8008ece919 | |||
| dbe031ca79 | |||
| be9f3b0d76 | |||
| 86006e408a | |||
| 6656841a01 | |||
| 682325de7d | |||
| 97e80814e3 | |||
| a4943f3fe4 | |||
| 5502c39219 | |||
| 89bbbedaee | |||
| 2a95f82c47 | |||
| a4501b4517 |
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,51 +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
|
||||
|
||||
# clangtidy:
|
||||
# stage: lint
|
||||
# script:
|
||||
# - curl https://docker.psi.ch:5000/v2/_catalog
|
||||
# # - dnf update -y
|
||||
# # - dnf install -y clang-tools-extra
|
||||
# # - clang-tidy sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h -checks=cppcoreguidelines-*,cert-*
|
||||
# # tags:
|
||||
# # - sinq
|
||||
|
||||
build_module:
|
||||
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
|
||||
674
LICENSE.txt
Normal file
674
LICENSE.txt
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
18
Makefile
18
Makefile
@@ -6,17 +6,27 @@ BUILDCLASSES=Linux
|
||||
EPICS_VERSIONS=7.0.7
|
||||
ARCH_FILTER=RHEL%
|
||||
|
||||
# additional module dependencies
|
||||
REQUIRED+=asynMotor
|
||||
# Specify the version of asynMotor we want to build against
|
||||
asynMotor_VERSION=7.2.2
|
||||
|
||||
# Source files to build
|
||||
SOURCES += src/msgPrintControl.cpp
|
||||
SOURCES += src/sinqAxis.cpp
|
||||
SOURCES += src/sinqController.cpp
|
||||
|
||||
# Headers which allow using this library in concrete driver implementations
|
||||
HEADERS += src/msgPrintControl.h
|
||||
HEADERS += src/sinqAxis.h
|
||||
HEADERS += src/sinqController.h
|
||||
|
||||
USR_CFLAGS += -Wall -Wextra # -Werror
|
||||
# Store the record files
|
||||
TEMPLATES += db/asynRecord.db
|
||||
TEMPLATES += db/sinqMotor.db
|
||||
|
||||
# MISCS would be the place to keep the stream device template files
|
||||
# This file registers the motor-specific functions in the IOC shell.
|
||||
DBDS += src/sinqMotor.dbd
|
||||
|
||||
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
|
||||
|
||||
405
README.md
405
README.md
@@ -2,38 +2,407 @@
|
||||
|
||||
## 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 functionalities.
|
||||
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.
|
||||
|
||||
## Features
|
||||
## User guide
|
||||
|
||||
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-file. All of these functions can be overwritten manually if e.g. a completely different implementation of `poll` is required.
|
||||
### Architecture of EPICS motor drivers at SINQ
|
||||
|
||||
### sinqController
|
||||
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:
|
||||
`asynController -> sinqController -> aController`
|
||||
`asynAxis -> sinqAxis -> aAxis`
|
||||
|
||||
`asynController -> sinqController -> bController`
|
||||
`asynAxis -> sinqAxis -> bAxis`
|
||||
|
||||
`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
|
||||
|
||||
Versioning of `sinqMotor` and of derived drivers follows the SemVer standard
|
||||
(https://semver.org/lang/de/). This standard is also used by the "require"
|
||||
extension for EPICS (https://github.com/paulscherrerinstitute/require) used at
|
||||
SINQ.
|
||||
|
||||
### IOC startup script
|
||||
|
||||
An EPICS IOC for motor control at SINQ is started by executing a script with the
|
||||
IOC shell. In its simplest form, an IOC for two controllers run by
|
||||
"exampleDriver" is a file looking like this:
|
||||
```
|
||||
#!/usr/local/bin/iocsh
|
||||
|
||||
# Load libraries needed for the IOC
|
||||
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
|
||||
< 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
|
||||
files `exampleDriver1.cmd` or `exampleDriver2` then look like this:
|
||||
|
||||
```
|
||||
# Define the name of the controller and the corresponding port
|
||||
epicsEnvSet("DRIVER_PORT","exampleDriver1")
|
||||
epicsEnvSet("IP_PORT","p$(DRIVER_PORT)")
|
||||
|
||||
# Create the TCP/IP socket used to talk with the controller. The socket can be
|
||||
# adressed from within the IOC shell via the port name
|
||||
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.
|
||||
# The other parameters are as follows:
|
||||
# 8: Maximum number of axes
|
||||
# 0.05: Busy poll period in seconds
|
||||
# 1: Idle poll period in seconds
|
||||
# 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!
|
||||
actualDriverAxis("$(DRIVER_PORT)",1);
|
||||
actualDriverAxis("$(DRIVER_PORT)",2);
|
||||
actualDriverAxis("$(DRIVER_PORT)",5);
|
||||
|
||||
# Set the number of subsequent timeouts
|
||||
setMaxSubsequentTimeouts("$(DRIVER_PORT)", 20);
|
||||
|
||||
# Set the number of forced fast polls performed after the poller is "woken up".
|
||||
# When the poller is "woken up", it performs the specified number of polls with
|
||||
# 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.
|
||||
setThresholdComTimeout("$(DRIVER_PORT)", 300, 10);
|
||||
|
||||
# Parametrize the EPICS record database with the substitution file named after the motor controller.
|
||||
epicsEnvSet("SINQDBPATH","$(exampleDriver_DB)/sinqMotor.db")
|
||||
dbLoadTemplate("$(TOP)/$(DRIVER_PORT).substitutions", "INSTR=$(INSTR)$(DRIVER_PORT):,CONTROLLER=$(DRIVER_PORT)")
|
||||
epicsEnvSet("SINQDBPATH","$(exampleDriver_DB)/turboPmac.db")
|
||||
dbLoadTemplate("$(TOP)/$(DRIVER_PORT).substitutions", "INSTR=$(INSTR)$(DRIVER_PORT):,CONTROLLER=$(DRIVER_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,
|
||||
`exampleDriver1.substitutions` needs to look like this (the order of columns
|
||||
does not matter):
|
||||
```
|
||||
file "$(SINQDBPATH)"
|
||||
{
|
||||
pattern
|
||||
{ AXIS, M, DESC, EGU, DIR, MRES, MSGTEXTSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED, ADAPTPOLL }
|
||||
{ 1, "lin1", "Linear motor doing whatever", mm, Pos, 0.001, 200, 1, 1.0, 1, 1 }
|
||||
{ 2, "rot1", "First rotary motor", degree, Neg, 0.001, 200, 0, 1.0, 0, 1 }
|
||||
{ 3, "rot2", "Second rotary motor", degree, Pos, 0.001, 200, 0, 0.0, 1, 0 }
|
||||
{ 5, "rot3", "Surprise: Third rotary motor", degree, Pos, 0.001, 200, 1, 2.0, 0, 0 }
|
||||
}
|
||||
```
|
||||
The variable `SINQDBPATH` has been set in "mcu1.cmd" before calling `dbLoadTemplate`.
|
||||
|
||||
#### 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.
|
||||
- `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).
|
||||
|
||||
#### 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.
|
||||
- `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.
|
||||
|
||||
### Motor record resolution MRES
|
||||
|
||||
The motor record resolution (index motorRecResolution_ in the parameter
|
||||
library, MRES in the motor record) is NOT a conversion factor between
|
||||
user units (e.g. mm) and motor units (e.g. encoder steps), but a scaling
|
||||
factor defining the resolution of the position readback field RRBV. This
|
||||
is due to an implementation detail of EPICS described here:
|
||||
https://epics.anl.gov/tech-talk/2018/msg00089.php
|
||||
https://github.com/epics-modules/motor/issues/8
|
||||
|
||||
Basically, the position value in the parameter library is a double which
|
||||
is then truncated to an integer in devMotorAsyn.c (because it was
|
||||
originally meant for converting from engineering units to encoder steps,
|
||||
which are by definition integer values). Therefore, if we want a
|
||||
precision of 1 millimeter, we need to set MRES to 1. If we want one of
|
||||
1 micrometer, we need to set MRES to 0.001. The readback value needs to
|
||||
be multiplied with MRES to get the actual value.
|
||||
|
||||
In the driver, we use user units. Therefore, when we interact with the
|
||||
parameter library, we need to account for MRES. This means:
|
||||
- When writing position or speed to the parameter library, we divide the
|
||||
value by the motor record resolution.
|
||||
- When reading position or speed from the parameter library, we multiply
|
||||
the value with the motor record resolution.
|
||||
|
||||
Index and motor record field are coupled as follows:
|
||||
The parameter motorRecResolution_ is coupled to the field MRES of the
|
||||
motor record in the following manner:
|
||||
- In sinqMotor.db, the PV (motor_record_pv_name) MOTOR_REC_RESOLUTION
|
||||
is defined as a copy of the field (motor_record_pv_name).MRES .
|
||||
- The PV name MOTOR_REC_RESOLUTION is coupled in asynMotorController.h
|
||||
to the constant motorRecResolutionString
|
||||
- ... which in turn is assigned to motorRecResolution_ in
|
||||
asynMotorController.cpp This way of making the field visible to the
|
||||
driver is described here:
|
||||
https://epics.anl.gov/tech-talk/2020/msg00378.php This is a one-way
|
||||
coupling, changes to the parameter library via setDoubleParam are NOT
|
||||
transferred to (motor_record_pv_name).MRES or to
|
||||
(motor_record_pv_name):Resolution.
|
||||
|
||||
### Additional records
|
||||
|
||||
`sinqMotor` provides a variety of additional records. See `db/sinqMotor.db` for
|
||||
the complete list and the documentation.
|
||||
|
||||
## Developer guide
|
||||
|
||||
### File structure
|
||||
|
||||
sinqMotor offers a variety of additional methods for children classes to
|
||||
standardize certain patterns (e.g. writing messages to the IOC shell and the
|
||||
motor message PV). For a detailed description, please see the respective
|
||||
function documentation in the .h-files. All of these functions can be
|
||||
overwritten manually if e.g. a completely different implementation of `poll` is
|
||||
required. Some functions are marked as virtual, because they are called from
|
||||
other functions of sinqMotor and therefore need runtime polymorphism. Functions
|
||||
without that marker are not called anywhere in sinqMotor (except for
|
||||
`forcedPoll`, which is called in `poll`).
|
||||
|
||||
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 deviceresponse failed.
|
||||
- `paramLibAccessFailed`: Write a standardized message if accessing the parameter library failed.
|
||||
- `stringifyAsynStatus`: Convert the enum `asynStatus` into a human-readable string.
|
||||
- `errMsgCouldNotParseResponse`: Write a standardized message if parsing a device response failed
|
||||
- `paramLibAccessFailed`: Write a standardized message if accessing the parameter library failed
|
||||
- `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
|
||||
- `atFirstPoll`: This function is executed once before the first poll. If it returns anything but `asynSuccess`, it retries.
|
||||
- `poll`: This is a wrapper around `doPoll` which performs some bookkeeping tasks before and after calling `doPoll`:
|
||||
#### sinqAxis.h
|
||||
- `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.
|
||||
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 `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`:
|
||||
- Try to execute `atFirstPoll` once (and retry, if that failed)
|
||||
- Check if the paramLib already contains an old error message. If so, put it
|
||||
into a temporary bufffer
|
||||
|
||||
After calling `doPoll`:
|
||||
- Reset `motorStatusProblem_`, `motorStatusCommsError_` and `motorMessageText_` if `doPoll` returned `asynSuccess`
|
||||
- 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.
|
||||
- Run `callParamCallbacks`
|
||||
- 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`
|
||||
- `doPoll`: This is an empty function which should be overwritten by concrete driver implementations.
|
||||
- `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.
|
||||
- `checkMovTimeoutWatchdog`: Check if the watchdog timed out.
|
||||
- `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.
|
||||
|
||||
## Versioning
|
||||
#### 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.
|
||||
|
||||
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.
|
||||
#### macros.h
|
||||
Contains macros used in `sinqMotor` and derived drivers:
|
||||
|
||||
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`, where X.X is the name of your version. To create a new tag, use `git tag -a X.X`. If the tag `X.X` is already used by another commit, git will show a corresponding error.
|
||||
- **HIDDEN**
|
||||
|
||||
## How to build it
|
||||
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`).
|
||||
|
||||
The makefile in the top directory includes all necessary steps for compiling a shared library together with the header files into `/ioc/modules` (using the PSI EPICS build system).Therefore it is sufficient to run `make install -f Makefile` from the terminal.
|
||||
Derived libraries can use the same mechanism via the macro `HIDDEN` (defined
|
||||
in `macros.h`):
|
||||
```
|
||||
class HIDDEN turboPmacController : public sinqController
|
||||
```
|
||||
|
||||
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.
|
||||
### 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.
|
||||
|
||||
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:
|
||||
- `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
|
||||
- `make install`
|
||||
|
||||
### 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`.
|
||||
|
||||
To use the library when writing a concrete motor driver, include it in the
|
||||
makefile of your application / library the same way as other libraries such as
|
||||
e.g. `asynMotor` by adding `REQUIRED+=sinqMotor` to your Makefile. The version
|
||||
can be specified with `sinqMotor_VERSION=x.x.x.`
|
||||
|
||||
### Usage as static dependency
|
||||
|
||||
This repository is included as a git submodule in the driver repositories
|
||||
depending upon sinqMotor. When installing via a Makefile (`make install`) using
|
||||
the PSI build system, the following git command is executed within
|
||||
`/ioc/tools/driver.makefile`:
|
||||
|
||||
`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`:
|
||||
|
||||
- `cd sinqMotor`
|
||||
- `git checkout 1.0`
|
||||
- `cd ..`
|
||||
|
||||
Then, the fixation of the version to 1.0 needs to be committed in the parent
|
||||
repository:
|
||||
|
||||
- `git commit -m "Update sinqMotor to 1.0"`
|
||||
|
||||
After this commit, running `make install` will use the correct driver version
|
||||
for compilation.
|
||||
|
||||
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.
|
||||
|
||||
11
db/asynRecord.db
Executable file
11
db/asynRecord.db
Executable file
@@ -0,0 +1,11 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
record(asyn,"$(P)")
|
||||
{
|
||||
field(DTYP,"asynRecordDevice")
|
||||
field(PORT,"$(PORT)")
|
||||
field(ADDR,"0")
|
||||
field(OMAX,"80")
|
||||
field(IMAX,"80")
|
||||
}
|
||||
|
||||
349
db/sinqMotor.db
Executable file
349
db/sinqMotor.db
Executable file
@@ -0,0 +1,349 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
# The main asyn motor record. Some fields are populated from the substitution
|
||||
# files via macros:
|
||||
# - INSTR: Name of the instrument, e.g. "SQ:SINQTEST:"
|
||||
# - M: Name of the motor in EPICS, e.g. "lin1"
|
||||
# - DESC: Short description of the motor. If not given, this is equal to M
|
||||
# - DIR: This value is usually set to "Pos". If the motor axis direction
|
||||
# should be inverted, this value can be set to "Neg"
|
||||
# - CONTROLLER: Name of the motor controller, e.g. "mcu1"
|
||||
# - AXIS: Number of the axis, e.g. "1"
|
||||
# - MRES: Motor record resolution. See the README.md for a detailed discussion
|
||||
# - EGU: Engineering units. In case of a rotary axis, this is "degree", in
|
||||
# case of a linear axis this is "mm".
|
||||
# - RTRY: The maximum number of times the motor record will try again to move to
|
||||
# the desired position. When the retry limit is reached, the motor record will
|
||||
# declare the motion finished. If the desired position was not reached, the
|
||||
# field MISS will be set to 1 and NICOS will emit a warning "Did not reach
|
||||
# target position". If this value is set to 0, the retry deadband is never
|
||||
# applied and therefore MISS will always be 0. The error message "Did not reach
|
||||
# target position" will therefore never appear.
|
||||
# - RDBD: Retry deadband: When the motor has finished a complete motion,
|
||||
# possibly including backlash takeout, the motor record will compare its current
|
||||
# position with the desired position. If the magnitude of the difference is
|
||||
# greater than RDBD, the motor will try again, as if the user had requested a
|
||||
# move from the now current position to the desired position. Only a limited
|
||||
# number of retries will be performed (see RTRY). If the given value is smaller
|
||||
# than MRES, it is set to MRES. In this version of the record, we set RDBD to a
|
||||
# very high value in order to suppress both retries and the NTM (new target
|
||||
# monitor) logic from issuing stop commands during overshoots (see
|
||||
# https://epics.anl.gov/bcda/synApps/motor/motorRecord.html#Fields_misc).
|
||||
record(motor,"$(INSTR)$(M)")
|
||||
{
|
||||
field(DESC,"$(DESC=$(M))")
|
||||
field(DTYP,"asynMotor")
|
||||
field(DIR,"$(DIR=Pos)")
|
||||
field(OUT,"@asyn($(CONTROLLER),$(AXIS))")
|
||||
field(MRES,"$(MRES)")
|
||||
field(EGU,"$(EGU)")
|
||||
field(INIT,"")
|
||||
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
|
||||
field(BDST, "0")
|
||||
field(RMOD,"3") # Retry mode 3 ("In-Position"): This suppresses any retries from the motor record.
|
||||
}
|
||||
|
||||
# This PV reads out the 10th bit of the MSTA field of the motor record, which
|
||||
# is the "motorStatusProblem_" bit.
|
||||
record(calc, "$(INSTR)$(M):StatusProblem")
|
||||
{
|
||||
field(INPA, "$(INSTR)$(M).MSTA CP")
|
||||
field(CALC, "A >> 9")
|
||||
}
|
||||
|
||||
# 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.
|
||||
record(longin, "$(INSTR)$(M):Connected")
|
||||
{
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_CONNECTED")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(PINI, "NO")
|
||||
field(VAL, "1")
|
||||
}
|
||||
|
||||
# Call the reset function of the corresponding sinqAxis
|
||||
# This record is coupled to the parameter library via motorReset_ -> MOTOR_RESET.
|
||||
record(longout, "$(INSTR)$(M):Reset") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_RESET")
|
||||
field(PINI, "NO")
|
||||
}
|
||||
|
||||
# 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.
|
||||
record(longin, "$(INSTR)$(M):StopRBV")
|
||||
{
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_FORCE_STOP")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(FLNK, "$(INSTR)$(M):Stop2Field")
|
||||
}
|
||||
record(longout, "$(INSTR)$(M):Stop2Field") {
|
||||
field(DOL, "$(INSTR)$(M):StopRBV NPP")
|
||||
field(OUT, "$(INSTR)$(M).STOP")
|
||||
field(OMSL, "closed_loop")
|
||||
}
|
||||
|
||||
# This record forwards the motor record resolution MRES to the parameter library
|
||||
# entry "MOTOR_REC_RESOLUTION" (solution from https://epics.anl.gov/tech-talk/2020/msg00378.php)
|
||||
# The value of MRES is needed inside the driver for various calculations (e.g.
|
||||
# for calculating the estimated time of arrival inside the watchdog).
|
||||
record(ao,"$(INSTR)$(M):RecResolution") {
|
||||
field(DESC, "$(M) resolution")
|
||||
field(DOL, "$(INSTR)$(M).MRES CP")
|
||||
field(OMSL, "closed_loop")
|
||||
field(DTYP, "asynFloat64")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_REC_RESOLUTION")
|
||||
}
|
||||
|
||||
# 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.
|
||||
record(waveform, "$(INSTR)$(M)-MsgTxt") {
|
||||
field(DTYP, "asynOctetRead")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_MESSAGE_TEXT")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "$(MSGTEXTSIZE=200)") # Should be the same as MAXBUF in the driver code
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
# 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.
|
||||
record(longout, "$(INSTR)$(M):Enable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ENABLE")
|
||||
field(PINI, "NO")
|
||||
}
|
||||
|
||||
# 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.
|
||||
record(longin, "$(INSTR)$(M):EnableRBV") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ENABLE_RBV")
|
||||
field(PINI, "NO")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
# 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.
|
||||
record(longin, "$(INSTR)$(M):CanDisable") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_CAN_DISABLE")
|
||||
field(PINI, "NO")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
|
||||
# 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.
|
||||
record(longout, "$(INSTR)$(M):CanSetSpeed") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_CAN_SET_SPEED")
|
||||
field(PINI, "YES")
|
||||
field(ASG, "READONLY") # Field is initialized during IOC startup
|
||||
field(VAL, "$(CANSETSPEED=0)")
|
||||
}
|
||||
|
||||
# If this PV has a value other than 0, adaptive polling for this axis is enabled.
|
||||
# The standard motor record behaviour is to poll all axis with the busy / move poll
|
||||
# period if at least one of the axes is moving. Adaptive polling changes this so
|
||||
# that only axes which were moving in the last poll are polled with the busy / move poll
|
||||
# period and all other axes are polled with the idle poll period.
|
||||
record(longout, "$(INSTR)$(M):AdaptivePolling") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) ADAPTIVE_POLLING")
|
||||
field(PINI, "YES")
|
||||
field(VAL, "$(ADAPTPOLL=1)")
|
||||
}
|
||||
|
||||
# The timeout mechanism for movements can be enabled / disabled by setting
|
||||
# this PV to 1 / 0.
|
||||
# This record is coupled to the parameter library via motorEnableMovWatchdog -> MOTOR_ENABLE_MOV_WATCHDOG.
|
||||
record(longout, "$(INSTR)$(M):EnableMovWatchdog") {
|
||||
field(DTYP, "asynInt32")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ENABLE_MOV_WATCHDOG")
|
||||
field(PINI, "YES")
|
||||
field(VAL, "$(ENABLEMOVWATCHDOG=0)")
|
||||
}
|
||||
|
||||
# For modern controllers, the high and low limits of the axis are read out
|
||||
# directly from the hardware. However, since the axis might slightly
|
||||
# "overshoot" when moving to a position next to the limits, the hardware might
|
||||
# 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.
|
||||
record(ao, "$(INSTR)$(M):LimitsOffset") {
|
||||
field(DTYP, "asynFloat64")
|
||||
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_LIMITS_OFFSET")
|
||||
field(PINI, "YES")
|
||||
field(ASG, "READONLY") # Field is initialized during IOC startup
|
||||
field(VAL, "$(LIMITSOFFSET=0)")
|
||||
}
|
||||
|
||||
# This record pair reads the parameter library value for "motorHighLimitFromDriver_"
|
||||
# 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.
|
||||
record(ai, "$(INSTR)$(M):DHLM_RBV")
|
||||
{
|
||||
field(DTYP, "asynFloat64")
|
||||
field(VAL, "$(DHLM=0)")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_HIGH_LIMIT_FROM_DRIVER")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(FLNK, "$(INSTR)$(M):PushDHLM2Field")
|
||||
field(PINI, "NO")
|
||||
}
|
||||
record(ao, "$(INSTR)$(M):PushDHLM2Field") {
|
||||
field(DOL, "$(INSTR)$(M):DHLM_RBV NPP")
|
||||
field(OUT, "$(INSTR)$(M).DHLM")
|
||||
field(OMSL, "closed_loop")
|
||||
field(PINI, "NO")
|
||||
}
|
||||
|
||||
# This record pair reads the parameter library value for "motorLowLimitFromDriver_"
|
||||
# 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.
|
||||
record(ai, "$(INSTR)$(M):DLLM_RBV")
|
||||
{
|
||||
field(DTYP, "asynFloat64")
|
||||
field(VAL, "$(DLLM=0)")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_LOW_LIMIT_FROM_DRIVER")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(FLNK, "$(INSTR)$(M):PushDLLM2Field")
|
||||
field(PINI, "NO")
|
||||
}
|
||||
record(ao, "$(INSTR)$(M):PushDLLM2Field") {
|
||||
field(DOL, "$(INSTR)$(M):DLLM_RBV NPP")
|
||||
field(OUT, "$(INSTR)$(M).DLLM")
|
||||
field(OMSL, "closed_loop")
|
||||
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.
|
||||
record(ai, "$(INSTR)$(M):VELO_RBV")
|
||||
{
|
||||
field(DTYP, "asynFloat64")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_VELO_FROM_DRIVER")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(FLNK, "$(INSTR)$(M):PushVELO2Field")
|
||||
}
|
||||
record(ao, "$(INSTR)$(M):PushVELO2Field") {
|
||||
field(DOL, "$(INSTR)$(M):VELO_RBV NPP")
|
||||
field(OUT, "$(INSTR)$(M).VELO")
|
||||
field(OMSL, "closed_loop")
|
||||
}
|
||||
|
||||
# This record pair reads the parameter library value for "motorVbasFromDriver_"
|
||||
# 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.
|
||||
record(ai, "$(INSTR)$(M):VBAS_RBV")
|
||||
{
|
||||
field(DTYP, "asynFloat64")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_VBAS_FROM_DRIVER")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(FLNK, "$(INSTR)$(M):PushVBAS2Field")
|
||||
}
|
||||
record(ao, "$(INSTR)$(M):PushVBAS2Field") {
|
||||
field(DOL, "$(INSTR)$(M):VBAS_RBV NPP")
|
||||
field(OUT, "$(INSTR)$(M).VBAS")
|
||||
field(OMSL, "closed_loop")
|
||||
}
|
||||
|
||||
# This record pair reads the parameter library value for "motorVmaxFromDriver_"
|
||||
# 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.
|
||||
record(ai, "$(INSTR)$(M):VMAX_RBV")
|
||||
{
|
||||
field(DTYP, "asynFloat64")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_VMAX_FROM_DRIVER")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(FLNK, "$(INSTR)$(M):PushVMAX2Field")
|
||||
}
|
||||
record(ao, "$(INSTR)$(M):PushVMAX2Field") {
|
||||
field(DOL, "$(INSTR)$(M):VMAX_RBV NPP")
|
||||
field(OUT, "$(INSTR)$(M).VMAX")
|
||||
field(OMSL, "closed_loop")
|
||||
}
|
||||
|
||||
# This record pair reads the parameter library value for "motorAcclFromDriver_"
|
||||
# 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.
|
||||
record(ai, "$(INSTR)$(M):ACCL_RBV")
|
||||
{
|
||||
field(DTYP, "asynFloat64")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_ACCL_FROM_DRIVER")
|
||||
field(SCAN, "I/O Intr")
|
||||
field(FLNK, "$(INSTR)$(M):PushACCL2Field")
|
||||
}
|
||||
record(ao, "$(INSTR)$(M):PushACCL2Field") {
|
||||
field(DOL, "$(INSTR)$(M):ACCL_RBV NPP")
|
||||
field(OUT, "$(INSTR)$(M).ACCL")
|
||||
field(OMSL, "closed_loop")
|
||||
}
|
||||
|
||||
# Read out the encoder type in human-readable form. The output numbers are ASCII
|
||||
# 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" (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")
|
||||
field(INP, "@asyn($(CONTROLLER),$(AXIS),1) ENCODER_TYPE")
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "80")
|
||||
field(SCAN, "I/O Intr")
|
||||
}
|
||||
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
|
||||
115
src/msgPrintControl.cpp
Normal file
115
src/msgPrintControl.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "msgPrintControl.h"
|
||||
#include <unordered_map>
|
||||
|
||||
msgPrintControlKey::msgPrintControlKey(char *controller, int axisNo,
|
||||
const char *functionName, int line,
|
||||
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_.c_str(), line_);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
msgPrintControl::msgPrintControl() : map_(), suffix_{} {}
|
||||
|
||||
msgPrintControl::~msgPrintControl() = default;
|
||||
|
||||
bool msgPrintControl::shouldBePrinted(msgPrintControlKey &key, bool wantToPrint,
|
||||
asynUser *pasynUser) {
|
||||
|
||||
// Reset the suffix
|
||||
suffix_[0] = 0;
|
||||
|
||||
if (wantToPrint) {
|
||||
/*
|
||||
We want to print the message associated with key -> Check if the number
|
||||
of allowed repetitions is exceeded. If true, inform the user that
|
||||
further output is suppressed.
|
||||
*/
|
||||
if (map_.find(key) != map_.end()) {
|
||||
size_t repetitions = map_[key];
|
||||
if (repetitions < key.maxRepetitions_) {
|
||||
// Number of allowed repetitions not exceeded -> Printing the
|
||||
// message is ok.
|
||||
map_[key] = repetitions + 1;
|
||||
return true;
|
||||
} else if (repetitions == key.maxRepetitions_) {
|
||||
// Reached number of allowed repetitions -> Printing the message
|
||||
// is ok, but further trys are rejected.
|
||||
char formattedKey[100] = {0};
|
||||
key.format(formattedKey, sizeof(formattedKey));
|
||||
snprintf(suffix_, sizeof(suffix_),
|
||||
" Further repetition of this error message (key "
|
||||
"\"%s\") is suppressed.",
|
||||
formattedKey);
|
||||
map_[key] = repetitions + 1;
|
||||
return true;
|
||||
} else {
|
||||
// Exceeded number of allowed repetitions -> Do not print the
|
||||
// message
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Message is not yet in map -> create an entry so it is watched in
|
||||
// the future.
|
||||
map_[key] = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
} else {
|
||||
/*
|
||||
We do not want to print the message associated with key -> If the key is
|
||||
part of the map, set the counter back to zero.
|
||||
*/
|
||||
if (map_.find(key) != map_.end()) {
|
||||
if (map_[key] != 0) {
|
||||
if (pasynUser != nullptr) {
|
||||
char formattedKey[100] = {0};
|
||||
key.format(formattedKey, sizeof(formattedKey));
|
||||
asynPrint(
|
||||
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_.c_str(), key.line_, formattedKey);
|
||||
}
|
||||
map_[key] = 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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, maxRepetitions);
|
||||
return shouldBePrinted(key, wantToPrint, pasynUser);
|
||||
}
|
||||
|
||||
void msgPrintControl::resetCount(msgPrintControlKey &key, asynUser *pasynUser) {
|
||||
if (map_.find(key) != map_.end()) {
|
||||
if (map_[key] != 0) {
|
||||
if (pasynUser != nullptr) {
|
||||
char formattedKey[100] = {0};
|
||||
key.format(formattedKey, sizeof(formattedKey));
|
||||
asynPrint(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_.c_str(), key.line_, formattedKey);
|
||||
}
|
||||
map_[key] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *msgPrintControl::getSuffix() { return suffix_; }
|
||||
169
src/msgPrintControl.h
Normal file
169
src/msgPrintControl.h
Normal file
@@ -0,0 +1,169 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#ifndef msgPrintControl_H
|
||||
#define msgPrintControl_H
|
||||
|
||||
#define DefaultMaxRepetitions 4
|
||||
|
||||
// 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>
|
||||
|
||||
/**
|
||||
* @brief Class to identify a message print location. See the docstring of
|
||||
* `msgPrintControl` on how to use this key.
|
||||
*
|
||||
*/
|
||||
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_;
|
||||
|
||||
std::string functionName_;
|
||||
int line_;
|
||||
|
||||
/**
|
||||
* @brief Maximum number of times a message is printed before it is
|
||||
* suppressed. This number is not used as part of the hash.
|
||||
*
|
||||
*/
|
||||
size_t maxRepetitions_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementation of the hash functionality for `msgPrintControlKey`
|
||||
*
|
||||
*/
|
||||
namespace std {
|
||||
template <> struct hash<msgPrintControlKey> {
|
||||
size_t operator()(const msgPrintControlKey &obj) const {
|
||||
// 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<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);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
/**
|
||||
* @brief Class to control the number of repetitions of error messages
|
||||
*
|
||||
* This class is used to prevent excessive repetitions of identical error
|
||||
* messages. For example, if the communication between a controller and an
|
||||
* axis fails, a corresponding error message is created in each poll. This
|
||||
* could "flood" the IOC shell with noise. To prevent this, this class keeps
|
||||
* track of the number of subsequent error message repetition. Each message is
|
||||
* uniquely identified by `msgPrintControlKey`. The function `shouldBePrinted`
|
||||
* can be used in order to see if a message should be printed or not:
|
||||
*
|
||||
* ```
|
||||
* const char* controller = "MCU" // Name of the controller
|
||||
* int axisNo = 0; // Number of the axis
|
||||
* bool wantToPrint = evaluateConditions(...); *
|
||||
* if (msgPrintControl.shouldBePrinted(controller, axisNo, __PRETTY_FUNCTION__,
|
||||
* __LINE__, wantToPrint)) { asynPrint(...)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
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,
|
||||
* otherwise true. Counter is reset if `wantToPrint` is false.
|
||||
*
|
||||
* If the conditions for printing a message are met, `wantToPrint` must be
|
||||
* set to true. The function then checks if `maxRepetitions_` has been
|
||||
* exceeded. If yes, the function returns no, indicating that the message
|
||||
* should not be printed. If no, the number of repetitions stored in the map
|
||||
* is incremented and the function returns true, indicating that the message
|
||||
* should be printed.
|
||||
*
|
||||
* If the conditions for printing a message are not met, `wantToPrint` must
|
||||
* be set to false. This resets the map entry.
|
||||
*
|
||||
* @param key Key associated with the message, used to
|
||||
* identify individual messages
|
||||
* @param wantToPrint If the message associated with key should be
|
||||
* printed, this value should be true, otherwise false.
|
||||
* @param pasynUser If the problem has been resolved (wantToPrint =
|
||||
* false), a corresponding status message is printed using the given
|
||||
* asynUser. If this pointer is a nullptr, no message is printed.
|
||||
* @return bool If true, the message should be printed, if
|
||||
* false, it should not.
|
||||
*/
|
||||
bool shouldBePrinted(msgPrintControlKey &key, bool wantToPrint,
|
||||
asynUser *pasynUser);
|
||||
|
||||
/**
|
||||
* @brief Like `shouldBePrinted(msgPrintControlKey key, bool wantToPrint)`,
|
||||
* but constructs the key from the first four arguments.
|
||||
*
|
||||
* @param controller_
|
||||
* @param axisNo
|
||||
* @param fileName
|
||||
* @param line
|
||||
* @param wantToPrint
|
||||
* @param pasynUser
|
||||
*/
|
||||
bool shouldBePrinted(char *controller, int axisNo, const char *functionName,
|
||||
int line, bool wantToPrint, asynUser *pasynUser,
|
||||
size_t maxRepetitions = DefaultMaxRepetitions);
|
||||
|
||||
/**
|
||||
* @brief Reset the error message count incremented in `shouldBePrinted` for
|
||||
* the given key
|
||||
*
|
||||
* @param key Key associated with the message, used to
|
||||
* identify individual messages
|
||||
* @param pasynUser If the problem has been resolved (`wantToPrint =
|
||||
* false`), a corresponding status message is printed using the given
|
||||
* asynUser. If this pointer is a nullptr, no message is printed.
|
||||
*/
|
||||
void resetCount(msgPrintControlKey &key, asynUser *pasynUser);
|
||||
|
||||
char *getSuffix();
|
||||
|
||||
private:
|
||||
std::unordered_map<msgPrintControlKey, size_t> map_;
|
||||
char suffix_[300] = {0};
|
||||
};
|
||||
|
||||
#endif
|
||||
887
src/sinqAxis.cpp
887
src/sinqAxis.cpp
@@ -1,91 +1,860 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// The EPICS libaries do not follow -Weffc++
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Weffc++"
|
||||
|
||||
#include "epicsExport.h"
|
||||
#include "iocsh.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>
|
||||
|
||||
sinqAxis::sinqAxis(class sinqController *pC, int axis)
|
||||
: asynMotorAxis((asynMotorController *)pC, axis), pC_(pC) {
|
||||
#define getControllerMethod pController
|
||||
|
||||
bool initial_poll_ = true;
|
||||
int init_poll_counter_ = 0;
|
||||
struct sinqAxisImpl {
|
||||
// Internal variables used in the movement timeout watchdog
|
||||
time_t expectedArrivalTime;
|
||||
time_t offsetMovTimeout;
|
||||
|
||||
double scaleMovTimeout;
|
||||
bool watchdogMovActive;
|
||||
// Store the motor target position for the movement time calculation
|
||||
double targetPosition;
|
||||
|
||||
bool wasMoving;
|
||||
|
||||
/*
|
||||
Store the time since the last poll
|
||||
*/
|
||||
epicsTimeStamp lastPollTime;
|
||||
};
|
||||
|
||||
sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
|
||||
: 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;
|
||||
|
||||
/*
|
||||
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 "
|
||||
"(axis index %d is not in range 0 to %d)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, axisNo,
|
||||
pC->numAxes() - 1);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize the parameter library entry for the motor message text, because
|
||||
it is read during the first poll before it has been written to.
|
||||
*/
|
||||
status = setStringParam(pC_->motorMessageText(), "");
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
"(setting a parameter value failed "
|
||||
"with %s)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Motor is assumed to be enabled
|
||||
status = setIntegerParam(pC_->motorEnableRBV(), 1);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
"(setting a parameter value failed "
|
||||
"with %s)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// By default, motors cannot be disabled
|
||||
status = setIntegerParam(pC_->motorCanDisable(), 0);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
"(setting a parameter value failed "
|
||||
"with %s)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Provide a default value for the motor position.
|
||||
status = setDoubleParam(pC_->motorPosition(), 0.0);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
"(setting a parameter value failed "
|
||||
"with %s)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Assume that the motor is connected initially
|
||||
status = setIntegerParam(pC_->motorConnected(), 1);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
"(setting a parameter value failed "
|
||||
"with %s)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// We assume that the motor has no status problems initially
|
||||
status = setIntegerParam(pC_->motorStatusProblem(), 0);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
"(setting a parameter value failed "
|
||||
"with %s)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Set the homing-related flags
|
||||
status = setIntegerParam(pC_->motorStatusHome(), 0);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
"(setting a parameter value failed "
|
||||
"with %s)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
status = setIntegerParam(pC_->motorStatusHomed(), 0);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
"(setting a parameter value failed "
|
||||
"with %s)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
status = setIntegerParam(pC_->motorStatusAtHome(), 0);
|
||||
if (status != asynSuccess) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
|
||||
"(setting a parameter value failed "
|
||||
"with %s)\n. Terminating IOC",
|
||||
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(status));
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::atFirstPoll() { return asynSuccess; }
|
||||
sinqAxis::~sinqAxis() = default;
|
||||
|
||||
asynStatus sinqAxis::poll(bool *moving) {
|
||||
int adaptivePolling = 0;
|
||||
|
||||
/*
|
||||
If adaptive polling is enabled:
|
||||
- Check if the motor was moving during the last poll
|
||||
- If yes, perform the poll
|
||||
- If no, check if the last poll was at least an idlePollPeriod ago
|
||||
- If yes, perform the poll
|
||||
- If no, skip it
|
||||
*/
|
||||
getAxisParamChecked(this, adaptivePolling, &adaptivePolling);
|
||||
|
||||
// Using the EPICS timestamp here, see
|
||||
// https://docs.epics-controls.org/projects/base/en/latest/epicsTime_h.html#_CPPv414epicsTimeStamp
|
||||
|
||||
// Get the current time
|
||||
epicsTimeStamp ts;
|
||||
epicsTimeGetCurrent(&ts);
|
||||
|
||||
/*
|
||||
Check if both adaptive polling is enabled and no forced fast polls are still
|
||||
required.
|
||||
*/
|
||||
if (adaptivePolling != 0 && pC_->outstandingForcedFastPolls() == 0 &&
|
||||
!pSinqA_->wasMoving) {
|
||||
|
||||
// Add the idle poll period
|
||||
epicsTimeStamp earliestTimeNextPoll = pSinqA_->lastPollTime;
|
||||
epicsTimeAddSeconds(&earliestTimeNextPoll, pC_->idlePollPeriod());
|
||||
|
||||
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
|
||||
epicsTimeStamp ts;
|
||||
epicsTimeGetCurrent(&ts);
|
||||
pSinqA_->lastPollTime = ts;
|
||||
|
||||
// If this poll is the initial poll, check if the parameter library has
|
||||
// already been initialized. If not, force EPCIS to repeat the poll until
|
||||
// the initialization is complete (or until a timeout is reached). Once the
|
||||
// parameter library has been initialized, read configuration data from the
|
||||
// motor controller into it.
|
||||
if (initial_poll_) {
|
||||
poll_status = atFirstPoll();
|
||||
if (poll_status == asynSuccess) {
|
||||
initial_poll_ = false;
|
||||
} else {
|
||||
// Send a message to the IOC shell every 10 trials.
|
||||
init_poll_counter_ += 1;
|
||||
if (init_poll_counter_ % 10 == 0) {
|
||||
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\nRunning function 'atFirstPoll' "
|
||||
"failed %d times with error %s.",
|
||||
__PRETTY_FUNCTION__, __LINE__, init_poll_counter_,
|
||||
pC_->stringifyAsynStatus(poll_status));
|
||||
}
|
||||
/*
|
||||
If the "motorMessageText" record currently contains an error message, it
|
||||
should be shown for at least one poll period. To assure this, it is read out
|
||||
here from the paramLib into "waitingMessage". If no new error message was
|
||||
added to the parameter library at the end of the poll cycle, the
|
||||
"waitingMessage" is briefly put into the paramLib again, then the PVs are
|
||||
updated and then the message text is cleared again.
|
||||
*/
|
||||
getAxisParamChecked(this, motorMessageText,
|
||||
static_cast<char *>(waitingMessage));
|
||||
|
||||
// Wait for 100 ms until trying the entire poll again
|
||||
usleep(100000);
|
||||
return poll_status;
|
||||
}
|
||||
}
|
||||
// Clear the communication
|
||||
setAxisParamChecked(this, motorStatusCommsError, false);
|
||||
|
||||
// The poll function is just a wrapper around doPoll and
|
||||
// handles mainly the callParamCallbacks() function. This wrapper is used
|
||||
// to make sure callParamCallbacks() is called in case of a premature
|
||||
// return.
|
||||
/*
|
||||
The poll function is just a wrapper around doPoll and handles mainly the
|
||||
callParamCallbacks() function. This wrapper is used to make sure
|
||||
callParamCallbacks() is called in case of a premature return.
|
||||
*/
|
||||
poll_status = doPoll(moving);
|
||||
|
||||
// If the poll status is ok, reset the error indicators in the parameter
|
||||
// library
|
||||
if (poll_status == asynSuccess) {
|
||||
pl_status = setIntegerParam(pC_->motorStatusProblem_, false);
|
||||
if (pl_status != asynSuccess) {
|
||||
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
pl_status = setIntegerParam(pC_->motorStatusCommsError_, false);
|
||||
if (pl_status != asynSuccess) {
|
||||
pC_->paramLibAccessFailed(pl_status, "motorStatusCommsError_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
/*
|
||||
If the poll did not succeed OR if an error message is waiting, something
|
||||
went wrong and the motor has a status problem. Otherwise, delete the error
|
||||
message entry which is currently in the paramLib.
|
||||
*/
|
||||
if (poll_status != asynSuccess || waitingMessage[0] != '\0') {
|
||||
/*
|
||||
If doPoll cleared the error message paramLib entry, but an old message
|
||||
is still waiting, set the old message.
|
||||
*/
|
||||
getAxisParamChecked(this, motorMessageText,
|
||||
static_cast<char *>(newMessage));
|
||||
if (newMessage[0] == '\0') {
|
||||
setAxisParamChecked(this, motorMessageText,
|
||||
static_cast<char *>(waitingMessage));
|
||||
}
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
} else {
|
||||
// No errors are waiting -> Clear everything.
|
||||
setAxisParamChecked(this, motorMessageText, "");
|
||||
setAxisParamChecked(this, motorStatusProblem, false);
|
||||
}
|
||||
|
||||
pl_status = setStringParam(pC_->motorMessageText_, "");
|
||||
if (pl_status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
/*
|
||||
Motor is in homing mode, was moving, but is not moving anymore -> It can be
|
||||
assumed that the homing procedure is done.
|
||||
*/
|
||||
getAxisParamChecked(this, motorStatusHome, &homing);
|
||||
if (homing == 1 && !(*moving) && pSinqA_->wasMoving) {
|
||||
setAxisParamChecked(this, motorStatusHome, false);
|
||||
setAxisParamChecked(this, motorStatusHomed, true);
|
||||
setAxisParamChecked(this, motorStatusAtHome, true);
|
||||
}
|
||||
|
||||
// Update the wasMoving status
|
||||
if (pC_->outstandingForcedFastPolls() == 0) {
|
||||
pSinqA_->wasMoving = *moving;
|
||||
}
|
||||
|
||||
// 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
|
||||
// function should be called at the end of a poll implementation.
|
||||
pl_status = callParamCallbacks();
|
||||
if (pl_status != asynSuccess) {
|
||||
// If we can't communicate with the parameter library, it doesn't make
|
||||
// sense to try and upstream this to the user -> Just log the error
|
||||
asynPrint(
|
||||
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||
"%s => line %d:\ncallParamCallbacks failed with %s for axis %d",
|
||||
__PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(poll_status), axisNo_);
|
||||
bool wantToPrint = pl_status != asynSuccess;
|
||||
if (pC_->getMsgPrintControl().shouldBePrinted(
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, wantToPrint,
|
||||
pC_->pasynUser())) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line "
|
||||
"%d:\ncallParamCallbacks failed with %s.%s\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pC_->stringifyAsynStatus(poll_status),
|
||||
pC_->getMsgPrintControl().getSuffix());
|
||||
}
|
||||
if (wantToPrint) {
|
||||
poll_status = pl_status;
|
||||
}
|
||||
|
||||
/*
|
||||
Delete the error message AFTER updating the PVs so it is not there anymore
|
||||
during the next poll.
|
||||
*/
|
||||
setAxisParamChecked(this, motorMessageText, "");
|
||||
|
||||
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) {
|
||||
|
||||
// Status of parameter library operations
|
||||
asynStatus status = asynSuccess;
|
||||
double motorRecRes = 0.0;
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Store the target position internally
|
||||
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
|
||||
pSinqA_->targetPosition = position * motorRecRes;
|
||||
|
||||
status = doMove(position, relative, minVelocity, maxVelocity, acceleration);
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = assertConnected();
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Since the move command was successfull, we assume that the motor has
|
||||
// started its movement.
|
||||
setAxisParamChecked(this, motorStatusHomed, false);
|
||||
setAxisParamChecked(this, motorStatusAtHome, false);
|
||||
|
||||
// Needed for adaptive polling
|
||||
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;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::home(double minVelocity, double maxVelocity,
|
||||
double acceleration, int forwards) {
|
||||
|
||||
asynStatus status = asynSuccess;
|
||||
|
||||
status = doHome(minVelocity, maxVelocity, acceleration, forwards);
|
||||
|
||||
if (status == asynSuccess) {
|
||||
|
||||
setAxisParamChecked(this, motorStatusHome, true);
|
||||
setAxisParamChecked(this, motorStatusHomed, false);
|
||||
setAxisParamChecked(this, motorStatusAtHome, false);
|
||||
pSinqA_->wasMoving = true;
|
||||
|
||||
status = assertConnected();
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
return pC_->wakeupPoller();
|
||||
|
||||
} else if (status == asynError) {
|
||||
// asynError means that we tried to home an absolute encoder
|
||||
setAxisParamChecked(this, motorMessageText,
|
||||
"Can't home a motor with absolute encoder");
|
||||
|
||||
status = assertConnected();
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Update the motor record
|
||||
return callParamCallbacks();
|
||||
} else {
|
||||
// Bubble up all other problems
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::reset() {
|
||||
asynStatus status = doReset();
|
||||
|
||||
if (status == asynSuccess) {
|
||||
// Perform some fast polls
|
||||
pC_->wakeupPoller();
|
||||
}
|
||||
|
||||
status = assertConnected();
|
||||
if (status != asynSuccess) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::doReset() { return asynError; }
|
||||
|
||||
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;
|
||||
double motorRecRes = 0.0;
|
||||
|
||||
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
|
||||
|
||||
/*
|
||||
We cannot use getAxisParamChecked checked here, since the name of the index
|
||||
getter function of the controller and this function have the same name.
|
||||
Therefore we implement this manually
|
||||
*/
|
||||
status = pC_->getDoubleParam(axisNo(), pC_->motorPosition(), motorPos);
|
||||
if (status != asynSuccess) {
|
||||
return pC_->paramLibAccessFailed(status, "motorPosition", axisNo(),
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
*motorPos = *motorPos * motorRecRes;
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::setMotorPosition(double motorPos) {
|
||||
asynStatus status = asynSuccess;
|
||||
double motorRecRes = 0.0;
|
||||
|
||||
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
|
||||
setAxisParamChecked(this, motorPosition, motorPos / motorRecRes);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::assertConnected() {
|
||||
int connected = 0;
|
||||
getAxisParamChecked(this, motorConnected, &connected);
|
||||
if (connected == 0) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line "
|
||||
"%d:\nAxis is not connected, all commands are ignored.\n",
|
||||
pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
|
||||
asynStatus status = asynSuccess;
|
||||
int variableSpeed = 0;
|
||||
|
||||
// Can the speed of the motor be varied?
|
||||
getAxisParamChecked(this, motorCanSetSpeed, &variableSpeed);
|
||||
|
||||
if (variableSpeed == 1) {
|
||||
|
||||
// Check the inputs and create corresponding error messages
|
||||
if (vbas > vmax) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nLower speed "
|
||||
"limit vbas=%lf must not be smaller than upper limit "
|
||||
"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. Please call the support.");
|
||||
return asynError;
|
||||
}
|
||||
if (velo < vbas || velo > vmax) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nActual "
|
||||
"speed velo=%lf must be between lower limit vbas=%lf and "
|
||||
"upper limit vmax=%lf.\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
velo, vbas, vmax);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
setAxisParamChecked(this, motorVbasFromDriver, vbas);
|
||||
setAxisParamChecked(this, motorVeloFromDriver, velo);
|
||||
setAxisParamChecked(this, motorVmaxFromDriver, vmax);
|
||||
} else {
|
||||
// Set minimum and maximum speed equal to the set speed
|
||||
setAxisParamChecked(this, motorVbasFromDriver, velo);
|
||||
setAxisParamChecked(this, motorVeloFromDriver, velo);
|
||||
setAxisParamChecked(this, motorVmaxFromDriver, velo);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::setAcclField(double accl) {
|
||||
|
||||
if (accl <= 0.0) {
|
||||
return asynError;
|
||||
}
|
||||
|
||||
setAxisParamChecked(this, motorAcclFromDriver, accl);
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::setWatchdogEnabled(bool enable) {
|
||||
setAxisParamChecked(this, motorEnableMovWatchdog, enable);
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::startMovTimeoutWatchdog() {
|
||||
asynStatus pl_status;
|
||||
int enableMovWatchdog = 0;
|
||||
|
||||
getAxisParamChecked(this, motorEnableMovWatchdog, &enableMovWatchdog);
|
||||
|
||||
if (enableMovWatchdog == 1) {
|
||||
// These parameters are only needed in this branch
|
||||
double motorPos = 0.0;
|
||||
double motorVelocity = 0.0;
|
||||
double motorVelocityRec = 0.0;
|
||||
double motorAccel = 0.0;
|
||||
double motorAccelRec = 0.0;
|
||||
double motorRecRes = 0.0;
|
||||
time_t timeContSpeed = 0;
|
||||
time_t timeAccel = 0;
|
||||
|
||||
// Activate the watchdog
|
||||
pSinqA_->watchdogMovActive = true;
|
||||
|
||||
/*
|
||||
NOTE: This function must not be called in the constructor (e.g. in order
|
||||
to save the read result to the member variable earlier), since the
|
||||
parameter library is updated at a later stage!
|
||||
*/
|
||||
pl_status = motorPosition(&motorPos);
|
||||
if (pl_status != asynSuccess) {
|
||||
return pl_status;
|
||||
}
|
||||
|
||||
/*
|
||||
We use motorVelocity, which corresponds to the record field VELO.
|
||||
From https://epics.anl.gov/docs/APS2015/14-Motor-Record.pdf:
|
||||
* VELO = motorVelocity_ = Slew velocity
|
||||
* VBAS = motorVelBase_ = Only used for stepper motors to minimize
|
||||
resonance.
|
||||
As documented in
|
||||
https://epics.anl.gov/docs/APS2015/17-Motor-Driver.pdf, the
|
||||
following relations apply: motorVelBase = VBAS / MRES motorVelocity
|
||||
= VELO / MRES motorAccel = (motorVelocity - motorVelBase) / ACCL
|
||||
Therefore, we need to correct the values from the parameter library.
|
||||
*/
|
||||
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
|
||||
|
||||
// Read the velocity
|
||||
getAxisParamChecked(this, motorVelocity, &motorVelocityRec);
|
||||
|
||||
// Only calculate timeContSpeed if the motorVelocity has been populated
|
||||
// with a sensible value (e.g. > 0)
|
||||
if (pl_status == asynSuccess && motorVelocityRec > 0.0) {
|
||||
// Convert back to the value in the VELO field
|
||||
motorVelocity = motorVelocityRec * motorRecRes;
|
||||
if (pl_status == asynSuccess) {
|
||||
|
||||
timeContSpeed =
|
||||
std::ceil(std::fabs(pSinqA_->targetPosition - motorPos) /
|
||||
motorVelocity);
|
||||
}
|
||||
}
|
||||
|
||||
getAxisParamChecked(this, motorAccel, &motorAccelRec);
|
||||
if (pl_status == asynSuccess && motorVelocityRec > 0.0 &&
|
||||
motorAccelRec > 0.0) {
|
||||
|
||||
// Convert back to the value in the ACCL field
|
||||
motorAccel = motorVelocityRec / motorAccelRec;
|
||||
|
||||
// Calculate the time
|
||||
timeAccel = 2 * std::ceil(motorVelocity / motorAccel);
|
||||
}
|
||||
|
||||
// Calculate the expected arrival time
|
||||
pSinqA_->expectedArrivalTime =
|
||||
time(NULL) + pSinqA_->offsetMovTimeout +
|
||||
pSinqA_->scaleMovTimeout * (timeContSpeed + 2 * timeAccel);
|
||||
} else {
|
||||
pSinqA_->watchdogMovActive = false;
|
||||
}
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
|
||||
int enableMovWatchdog = 0;
|
||||
|
||||
getAxisParamChecked(this, motorEnableMovWatchdog, &enableMovWatchdog);
|
||||
|
||||
// Not moving or watchdog not active / enabled
|
||||
if (enableMovWatchdog == 0 || !moving || !pSinqA_->watchdogMovActive) {
|
||||
pSinqA_->watchdogMovActive = false;
|
||||
return asynSuccess;
|
||||
}
|
||||
|
||||
// Create the unique callsite identifier manually so it can be used later in
|
||||
// the shouldBePrinted calls.
|
||||
msgPrintControlKey key = msgPrintControlKey(pC_->portName, axisNo_,
|
||||
__PRETTY_FUNCTION__, __LINE__);
|
||||
|
||||
// Check if the expected time of arrival has been exceeded.
|
||||
if (pSinqA_->expectedArrivalTime < time(NULL)) {
|
||||
// Check the watchdog
|
||||
if (pC_->getMsgPrintControl().shouldBePrinted(key, true,
|
||||
pC_->pasynUser())) {
|
||||
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nExceeded "
|
||||
"expected arrival time %ld (current time is %ld).\n",
|
||||
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
|
||||
pSinqA_->expectedArrivalTime, time(NULL));
|
||||
}
|
||||
|
||||
setAxisParamChecked(
|
||||
this, motorMessageText,
|
||||
"Exceeded expected arrival time. Check if the axis is blocked.");
|
||||
setAxisParamChecked(this, motorStatusProblem, true);
|
||||
} else {
|
||||
pC_->getMsgPrintControl().resetCount(key, pC_->pasynUser());
|
||||
}
|
||||
|
||||
// Even if the movement timed out, the rest of the poll should continue.
|
||||
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" {
|
||||
|
||||
/**
|
||||
* @brief Enable / disable the watchdog (FFI implementation)
|
||||
*
|
||||
* @param portName Name of the controller
|
||||
* @param axisNo Axis number
|
||||
* @param enable If 0, disable the watchdog, otherwise enable
|
||||
* it
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus setWatchdogEnabled(const char *portName, int axisNo, int enable) {
|
||||
|
||||
sinqController *pC = (sinqController *)findAsynPortDriver(portName);
|
||||
if (pC == nullptr) {
|
||||
errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__, portName);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
|
||||
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
|
||||
if (axis == nullptr) {
|
||||
errlogPrintf(
|
||||
"Controller \"%s\", axis %d => %s, line %d:\nAxis does not "
|
||||
"exist or is not an instance of sinqAxis.",
|
||||
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
return axis->setWatchdogEnabled(enable != 0);
|
||||
}
|
||||
|
||||
static const iocshArg setWatchdogEnabledArg0 = {"Controller port name",
|
||||
iocshArgString};
|
||||
static const iocshArg setWatchdogEnabledArg1 = {"Axis number", iocshArgInt};
|
||||
static const iocshArg setWatchdogEnabledArg2 = {
|
||||
"Enabling / disabling the watchdog", iocshArgInt};
|
||||
static const iocshArg *const setWatchdogEnabledArgs[] = {
|
||||
&setWatchdogEnabledArg0, &setWatchdogEnabledArg1, &setWatchdogEnabledArg2};
|
||||
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);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @brief Set the offsetMovTimeout (FFI implementation)
|
||||
*
|
||||
* @param portName Name of the controller
|
||||
* @param axisNo Axis number
|
||||
* @param offsetMovTimeout Offset (in seconds)
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus setOffsetMovTimeout(const char *portName, int axisNo,
|
||||
double offsetMovTimeout) {
|
||||
|
||||
sinqController *pC;
|
||||
pC = (sinqController *)findAsynPortDriver(portName);
|
||||
if (pC == nullptr) {
|
||||
errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__, portName);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
|
||||
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
|
||||
if (axis == nullptr) {
|
||||
errlogPrintf("Controller \"%s\" => %s, line %d:\nAxis %d does not "
|
||||
"exist or is not an instance of sinqAxis.",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__, axisNo);
|
||||
}
|
||||
|
||||
return axis->setOffsetMovTimeout(offsetMovTimeout);
|
||||
}
|
||||
|
||||
static const iocshArg setOffsetMovTimeoutArg0 = {"Controller port name",
|
||||
iocshArgString};
|
||||
static const iocshArg setOffsetMovTimeoutArg1 = {"Axis number", iocshArgInt};
|
||||
static const iocshArg setOffsetMovTimeoutArg2 = {"Offset timeout for movement",
|
||||
iocshArgDouble};
|
||||
static const iocshArg *const setOffsetMovTimeoutArgs[] = {
|
||||
&setOffsetMovTimeoutArg0, &setOffsetMovTimeoutArg1,
|
||||
&setOffsetMovTimeoutArg2};
|
||||
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);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* @brief Set the setScaleMovTimeout (FFI implementation)
|
||||
*
|
||||
* @param portName Name of the controller
|
||||
* @param axisNo Axis number
|
||||
* @param scaleMovTimeout Scaling factor (in seconds)
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus setScaleMovTimeout(const char *portName, int axisNo,
|
||||
double scaleMovTimeout) {
|
||||
|
||||
sinqController *pC;
|
||||
pC = (sinqController *)findAsynPortDriver(portName);
|
||||
if (pC == nullptr) {
|
||||
errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__, portName);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
|
||||
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
|
||||
if (axis == nullptr) {
|
||||
errlogPrintf("Controller \"%s\" => %s, line %d:\nAxis %d does not "
|
||||
"exist or is not an instance of sinqAxis.",
|
||||
portName, __PRETTY_FUNCTION__, __LINE__, axisNo);
|
||||
return asynError;
|
||||
}
|
||||
|
||||
return axis->setScaleMovTimeout(scaleMovTimeout);
|
||||
}
|
||||
|
||||
static const iocshArg setScaleMovTimeoutArg0 = {"Controller port name",
|
||||
iocshArgString};
|
||||
static const iocshArg setScaleMovTimeoutArg1 = {"Axis number", iocshArgInt};
|
||||
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,
|
||||
"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);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
// This function is made known to EPICS in sinqMotor.dbd and is called by EPICS
|
||||
// in order to register all functions in the IOC shell
|
||||
static void sinqAxisRegister(void) {
|
||||
iocshRegister(&setOffsetMovTimeoutDef, setOffsetMovTimeoutCallFunc);
|
||||
iocshRegister(&setScaleMovTimeoutDef, setScaleMovTimeoutCallFunc);
|
||||
iocshRegister(&setWatchdogEnabledDef, setWatchdogEnabledCallFunc);
|
||||
}
|
||||
epicsExportRegistrar(sinqAxisRegister);
|
||||
|
||||
} // extern C
|
||||
|
||||
794
src/sinqAxis.h
794
src/sinqAxis.h
@@ -1,39 +1,797 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
This class extends asynMotorAxis by some features used in SINQ.
|
||||
|
||||
Stefan Mathis, November 2024
|
||||
*/
|
||||
#ifndef __SINQDRIVER
|
||||
#define __SINQDRIVER
|
||||
|
||||
#ifndef sinqAxis_H
|
||||
#define sinqAxis_H
|
||||
|
||||
// The EPICS libaries do not follow -Weffc++
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Weffc++"
|
||||
|
||||
#include "asynMotorAxis.h"
|
||||
#include "macros.h"
|
||||
|
||||
class epicsShareClass sinqAxis : public asynMotorAxis {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
struct sinqAxisImpl;
|
||||
|
||||
class HIDDEN sinqAxis : public asynMotorAxis {
|
||||
public:
|
||||
sinqAxis(class sinqController *pC_, int axis);
|
||||
/**
|
||||
* @brief Construct a new sinqAxis object
|
||||
*
|
||||
* @param pC_ Pointer to the controller of the axis
|
||||
* @param axis Index of the axis
|
||||
*/
|
||||
sinqAxis(class sinqController *pC_, int axisNo);
|
||||
|
||||
/**
|
||||
This function is executed at the very first poll after the IOC startup. If
|
||||
it returns anything else than 'asynSuccess', the function is evaluated again
|
||||
after 100 ms until it succeeds. Every 10 trials a warning is emitted.
|
||||
* @brief Destroy the sinqAxis object
|
||||
*
|
||||
* This destructor is necessary in order to use the PIMPL idiom.
|
||||
*/
|
||||
asynStatus atFirstPoll();
|
||||
|
||||
asynStatus poll(bool *moving);
|
||||
~sinqAxis();
|
||||
|
||||
/**
|
||||
Implementation of the "proper", device-specific poll method. This method
|
||||
should be implemented by a child class of sinqAxis.
|
||||
* @brief Delete the copy and copy assignment constructors, because this
|
||||
* class should not be copied (it is tied to hardware!)
|
||||
*/
|
||||
asynStatus doPoll(bool *moving);
|
||||
sinqAxis(const sinqAxis &) = delete;
|
||||
sinqAxis &operator=(const sinqAxis &) = delete;
|
||||
|
||||
friend class sinqController;
|
||||
/**
|
||||
* @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);
|
||||
|
||||
protected:
|
||||
bool initial_poll_;
|
||||
int init_poll_counter_;
|
||||
/**
|
||||
* @brief Perform some standardized operations before and after the concrete
|
||||
`doPoll` implementation.
|
||||
*
|
||||
* Wrapper around `doPoll` which performs the following operations:
|
||||
|
||||
- Call the `doPoll` method
|
||||
|
||||
- Reset motorStatusProblem_, motorStatusCommsError_ and motorMessageText_ if
|
||||
doPoll returned asynSuccess
|
||||
|
||||
- If the movement timeout watchdog has been started, check it.
|
||||
|
||||
- The flags `motorStatusHome_`, `motorStatusHomed_` and
|
||||
`motorStatusAtHome_` are set to their idle values (0, 1 and 1 respectively)
|
||||
in the `forcedPoll()` method once the homing procedure is finished. See the
|
||||
documentation of the `home()` method for more details.
|
||||
|
||||
- Run `callParamCallbacks()`
|
||||
|
||||
- Return the status of `doPoll`
|
||||
*
|
||||
* @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).
|
||||
*/
|
||||
asynStatus forcedPoll(bool *moving);
|
||||
|
||||
/**
|
||||
* @brief Implementation of the "proper", device-specific poll method. This
|
||||
method should be implemented by a child class of sinqAxis.
|
||||
*
|
||||
* @param moving Should be set to true, if the axis is moving,
|
||||
* and false otherwise.
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus doPoll(bool *moving);
|
||||
|
||||
/**
|
||||
* @brief Perform some standardized operations before and after the concrete
|
||||
`doMove` implementation.
|
||||
|
||||
* Wrapper around `doMove` which calculates the (absolute) target position
|
||||
and stores it in the member variable `targetPosition_`. This member variable
|
||||
is e.g. used for the movement watchdog. Afterwards, it calls and returns
|
||||
`doMove`.
|
||||
*
|
||||
* @param position Forwarded to `doMove`.
|
||||
* @param relative Forwarded to `doMove`.
|
||||
* @param minVelocity Forwarded to `doMove`.
|
||||
* @param maxVelocity Forwarded to `doMove`.
|
||||
* @param acceleration Forwarded to `doMove`.
|
||||
* @return asynStatus Forward the status of `doMove`, unless one of
|
||||
the parameter library operation fails (in that case, returns the failed
|
||||
operation status).
|
||||
*/
|
||||
virtual asynStatus move(double position, int relative, double minVelocity,
|
||||
double maxVelocity, double acceleration);
|
||||
|
||||
/**
|
||||
* @brief Implementation of the "proper", device-specific move method. This
|
||||
method should be implemented by a child class of sinqAxis.
|
||||
*
|
||||
* @param position Target position `VAL` from the motor record
|
||||
* @param relative Specifies, whether the target position is
|
||||
relative or absolute.
|
||||
* @param minVelocity Minimum velocity VMIN from the motor record
|
||||
* @param maxVelocity Maximum velocity VMAX from the motor record
|
||||
* @param acceleration Acceleration ACCEL from the motor record
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus doMove(double position, int relative, double minVelocity,
|
||||
double maxVelocity, double acceleration);
|
||||
|
||||
/**
|
||||
* @brief Wrapper around doHome which handles the homing-related flags
|
||||
*
|
||||
* The homing procedure of the motor record is controlled by the following
|
||||
* parameter library flags:
|
||||
*
|
||||
* - `motorMoveToHome_`: Setting this flag to `1` indicates to EPICS that a
|
||||
homing procedure should start and can therefore be used to start homing from
|
||||
within the driver.
|
||||
|
||||
* - `motorStatusHome_`: This flag should be set to `1` while the motor is
|
||||
actively moving toward its home position and to `0` when the home position
|
||||
is reached.
|
||||
*
|
||||
* - `motorStatusHomed_`: This flag should be set to `0` at the start of a
|
||||
homing command and to 1 once the home position is reached.
|
||||
*
|
||||
* - `motorStatusAtHome_`: This flag is similar to `motorStatusHomed_`, but
|
||||
in addition it should also be `1` when the motor is at its home position,
|
||||
but wasn't actively homed in order to get there.
|
||||
*
|
||||
* This function performs the following operations in the given order:
|
||||
*
|
||||
* - Call `doHome()` and forward the parameters
|
||||
*
|
||||
* - If `doHome()` returned asynSuccess: Set `motorStatusHome_` to `1`,
|
||||
`motorStatusHomed_` to `0` and `motorStatusAtHome_` to `0`.
|
||||
*
|
||||
* - If `doHome()` returned asynError: This means that the motor cannot be
|
||||
homed because the encoder is absolute. Set a corresponding error message,
|
||||
but return asynSuccess in order to avoid any automatic retries by asyn.
|
||||
|
||||
* - If `doHome()` returned anything else: Forward the status.
|
||||
*
|
||||
* The flags `motorStatusHome_`, `motorStatusHomed_` and
|
||||
`motorStatusAtHome_` are set to their idle values (0, 1 and 1 respectively)
|
||||
in the `forcedPoll())` method once the homing procedure is finished.
|
||||
*
|
||||
* @param minVelocity Forwarded to `doHome`.
|
||||
* @param maxVelocity Forwarded to `doHome`.
|
||||
* @param acceleration Forwarded to `doHome`.
|
||||
* @param forwards Forwarded to `doHome`.
|
||||
* @return asynStatus Forward the status of `doHome`, unless one of
|
||||
the parameter library operation fails (in that case, returns the failed
|
||||
operation status).
|
||||
*/
|
||||
virtual asynStatus home(double minVelocity, double maxVelocity,
|
||||
double acceleration, int forwards);
|
||||
|
||||
/**
|
||||
* @brief Implementation of the "proper", device-specific home method. This
|
||||
method should be implemented by a child class of sinqAxis. If the motor
|
||||
cannot be homed because it has an absolute encoder, this function should
|
||||
return asynError.
|
||||
*
|
||||
* @param minVelocity Minimum velocity VMIN from the motor record
|
||||
* @param maxVelocity Maximum velocity VMAX from the motor record
|
||||
* @param acceleration Acceleration ACCEL from the motor record
|
||||
* @param forwards Is 1, if the motor record field HOMF was used
|
||||
to trigger the homing, and 0, if HOMR was used.
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus doHome(double minVelocity, double maxVelocity,
|
||||
double acceleration, int forwards);
|
||||
|
||||
/**
|
||||
* @brief This function is called when the PV "$(INSTR)$(M):Reset" is set to
|
||||
* any value. It calls `doReset` (which ought to be implemented by a child
|
||||
* class) and then performs da defined number of consecutive fast polls. If
|
||||
* one of the polls returns asynSuccess, it returns immediately.
|
||||
*
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus reset();
|
||||
|
||||
/**
|
||||
* @brief Implementation of the "proper", device-specific `reset` method.
|
||||
This method should be implemented by a child class of sinqAxis. If the
|
||||
motor cannot be reset, this function should return asynError.
|
||||
*
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus doReset();
|
||||
|
||||
/**
|
||||
* @brief This function enables / disables an axis. It should be implemented
|
||||
* by a child class of sinqAxis.
|
||||
*
|
||||
* The concrete implementation should (but doesn't need to) follow the
|
||||
* convention that a value of 0 disables the axis and any other value
|
||||
* enables it.
|
||||
*
|
||||
* @param on
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus enable(bool on);
|
||||
|
||||
/**
|
||||
* @brief Populate the motor record fields VELO, VBAS and VMAX
|
||||
*
|
||||
* Populates the speed fields of the motor record. If the param lib
|
||||
* entry motorCanSetSpeed_ (connected to the PV x:VariableSpeed) is set to
|
||||
* 1, VBAS and VMAX are set to min and max respectively. Otherwise, they are
|
||||
* set to val. Additionally, the speed itself is set to VELO.
|
||||
*
|
||||
* The units of the inputs are engineering units (EGU) per second (e.g. mm/s
|
||||
* if the EGU is mm).
|
||||
*
|
||||
* If the given configuration is invalid (min > max, velo < min, velo > max)
|
||||
* and the motor is configured as a variable speed motor (param lib entry
|
||||
* motorCanSetSpeed_ is 1), this function returns an asynError.
|
||||
*
|
||||
* @param velo Actual velocity (EGU / s)
|
||||
* @param vbas Minimum allowed velocity (EGU / s)
|
||||
* @param velo Maximum allowed velocity (EGU / s)
|
||||
*
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus setVeloFields(double velo, double vbas, double vmax);
|
||||
|
||||
/**
|
||||
* @brief Populate the ACCL field of the motor record
|
||||
*
|
||||
* Populates the acceleration field of the motor record with the given
|
||||
* value. If accl is not positive, this function does not set the value and
|
||||
* returns an asynError.
|
||||
*
|
||||
* The unit of the input is engineering units (EGU) per second squared (e.g.
|
||||
* mm/s^2 if the EGU is mm).
|
||||
*
|
||||
* @param accl Actual acceleration (EGU / s^2)
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus setAcclField(double accl);
|
||||
|
||||
/**
|
||||
* @brief Start the watchdog for the movement, if the watchdog is not
|
||||
disabled. See the documentation of checkMovTimeoutWatchdog for more details.
|
||||
*
|
||||
* @return asynStatus If one of the parameter library operations
|
||||
used to get the values for the timeout calculation failed, return that
|
||||
status, otherwise return asynSuccess.
|
||||
*/
|
||||
virtual asynStatus startMovTimeoutWatchdog();
|
||||
|
||||
/**
|
||||
* @brief Check if the watchdog timed out
|
||||
*
|
||||
Manages a timeout mechanism for the movement:
|
||||
If the axis is moving and the movement takes too long, create an error
|
||||
message and return asynError. The watchdog is started when moving switches
|
||||
from "false" to "true" and stopped when moving switches from "true" to
|
||||
"false". At the watchdog start, the estimated movement time is calculated as
|
||||
|
||||
t = offsetMovTimeout_ + scaleMovTime_ * [timeContSpeed + 2*timeAccel]
|
||||
|
||||
with
|
||||
|
||||
timeContSpeed = abs(targetPosition - motorPosition) / motorVelBase
|
||||
timeAcc = motorVelBase / motorAccel
|
||||
|
||||
The values motorVelBase, motorAccel and positionAtMovementStart are taken
|
||||
from the parameter library. Therefore it is necessary to populate them
|
||||
before using this function. If they are not given, both speed and velocity
|
||||
are assumed to be infinite. This means that timeContSpeed and/or timeAcc are
|
||||
set to zero. targetPosition is populated automatically when using the doMove
|
||||
function.
|
||||
|
||||
The values offsetMovTimeout_ and scaleMovTimeout_ can be set directly from
|
||||
the IOC shell with the functions setScaleMovTimeout and setOffsetMovTimeout,
|
||||
if sinqMotor is loaded via the "require" mechanism.
|
||||
*
|
||||
* @param moving Should be the "moving" status from `poll` /
|
||||
`doPoll`.
|
||||
* @return asynStatus Return asynError, if the watchdog timed out,
|
||||
and asynSuccess otherwise.
|
||||
*/
|
||||
virtual asynStatus checkMovTimeoutWatchdog(bool moving);
|
||||
|
||||
/**
|
||||
* @brief Enable / disable the watchdog. Also available in the IOC shell
|
||||
* (see "extern C" section in sinqController.cpp).
|
||||
*
|
||||
* If enable is set to false and the watchdog is currently running, this
|
||||
* function stops it immediately.
|
||||
*
|
||||
* @param enabled
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus setWatchdogEnabled(bool enable);
|
||||
|
||||
/**
|
||||
* @brief Set the offsetMovTimeout. Also available in the IOC shell
|
||||
* (see "extern C" section in sinqController.cpp).
|
||||
*
|
||||
* See documentation of `checkMovTimeoutWatchdog` for details.
|
||||
*
|
||||
* @param offsetMovTimeout Offset (in seconds)
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus setOffsetMovTimeout(time_t offsetMovTimeout);
|
||||
|
||||
/**
|
||||
* @brief Set the scaleMovTimeout. Also available in the IOC shell
|
||||
* (see "extern C" section in sinqController.cpp).
|
||||
*
|
||||
See documentation of `checkMovTimeoutWatchdog` for details.
|
||||
*
|
||||
* @param scaleMovTimeout Scaling factor (in seconds)
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus setScaleMovTimeout(time_t scaleMovTimeout);
|
||||
|
||||
/**
|
||||
* @brief Return the axis number of this axis
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int axisNo() { return axisNo_; }
|
||||
|
||||
/**
|
||||
* @brief Read the motor position from the paramLib, adjusted for the
|
||||
* motorRecResolution
|
||||
*
|
||||
* The motorPosition value in the paramLib is the encoder position
|
||||
* divided by the motorRecResolution (see README.md). This function
|
||||
* fetches the paramLib value and multiplies it with motorRecResolution
|
||||
* (also fetched from the paramLib).
|
||||
*
|
||||
* @param motorPositon
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus motorPosition(double *motorPositon);
|
||||
|
||||
/**
|
||||
* @brief Write the motor position in the paramLib, adjusted for the
|
||||
* motorRecResolution
|
||||
*
|
||||
* The motorPosition value in the paramLib is the encoder position
|
||||
* divided by the motorRecResolution (see README.md). This function takes
|
||||
* the input value and divides it with motorRecResolution (fetched from
|
||||
* the paramLib).
|
||||
*
|
||||
* @param motorPosition
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus setMotorPosition(double motorPosition);
|
||||
|
||||
/**
|
||||
* @brief Check if the axis is not connected and print a corresponding error
|
||||
* message
|
||||
*
|
||||
* This method is meant to be used at the end of "interactive" function
|
||||
* calls such as move, home, stop etc which can be manually triggered from
|
||||
* the IOC shell or from the channel access protocol.
|
||||
*/
|
||||
asynStatus assertConnected();
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
};
|
||||
* ```
|
||||
*/
|
||||
virtual sinqController *pController() { return pC_; };
|
||||
|
||||
/**
|
||||
* @brief Returns true, if the axis was moving in the last poll cycle, and
|
||||
* false otherwise.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool wasMoving();
|
||||
|
||||
/**
|
||||
* @brief Override the wasMoving flag (normally, it is automatically updated
|
||||
* during each poll).
|
||||
*
|
||||
*/
|
||||
void setWasMoving(bool wasMoving);
|
||||
|
||||
/**
|
||||
* @brief Read out the last received target position in engineering units.
|
||||
*
|
||||
* @return double
|
||||
*/
|
||||
double targetPosition();
|
||||
|
||||
/**
|
||||
* @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_;
|
||||
};
|
||||
|
||||
#endif
|
||||
// =============================================================================
|
||||
// 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
|
||||
*
|
||||
* 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
|
||||
* @param callerFunctionName
|
||||
* @param lineNumber
|
||||
* @return asynStatus
|
||||
*/
|
||||
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
|
||||
*
|
||||
* This macro is a wrapper around `setIntegerParam` / `setDoubleParam` /
|
||||
* `setStringParam` which checks if the operation was successfull. If it wasn't,
|
||||
* it returns by calling the paramLibAccessFailed function.
|
||||
*
|
||||
* For example, the following input:
|
||||
* ```
|
||||
* setAxisParamChecked(this, motorStatusProblem_, false)
|
||||
* ```
|
||||
* expands into the following code:
|
||||
* ```
|
||||
* {
|
||||
* int indexValue = controller->motorStatusProblem_();
|
||||
* asynStatus status = axis->setIntegerParam(indexValue, writeValue);
|
||||
* if (status != asynSuccess) {
|
||||
* return controller->paramLibAccessFailed(
|
||||
* status, "motorStatusProblem_", axis->axisNo(), __PRETTY_FUNCTION__,
|
||||
* __LINE__);
|
||||
* }
|
||||
* return 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 get an integer / double / string parameter for an
|
||||
* axis in the paramLib
|
||||
*
|
||||
* This function should not be used directly, but rather through its macro
|
||||
* variant `getAxisParamChecked`.
|
||||
*
|
||||
* @tparam T
|
||||
* @param axis
|
||||
* @param controller
|
||||
* @param indexName
|
||||
* @param func
|
||||
* @param readValue
|
||||
* @param callerFunctionName
|
||||
* @param lineNumber
|
||||
* @param maxChars Only used when readValue is a char*. Specifies the maximum
|
||||
* number of characters which can be placed into the buffer the pointer points
|
||||
* to.
|
||||
* @return asynStatus
|
||||
*/
|
||||
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. In order
|
||||
*
|
||||
* For example, the following input:
|
||||
* ```
|
||||
* getAxisParamChecked(this, motorStatusProblem_, &readValue)
|
||||
* ```
|
||||
* expands into the following code:
|
||||
* ```
|
||||
* {
|
||||
* int indexValue = controller->motorStatusProblem_();
|
||||
* asynStatus status = controller->getIntegerParam(axis->axisNo(),
|
||||
* indexValue, readValue); if (status != asynSuccess) { return
|
||||
* controller->paramLibAccessFailed( status, "motorStatusProblem_",
|
||||
* axis->axisNo(), __PRETTY_FUNCTION__,
|
||||
* __LINE__);
|
||||
* }
|
||||
* return asynSuccess;
|
||||
* }
|
||||
* ```
|
||||
* =============================================================================
|
||||
*/
|
||||
#define getAxisParamChecked(axis, indexGetterFunction, readValue) \
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,53 +1,420 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
This class contains the necessary changes to have an additional text fields
|
||||
for messages with each axis.
|
||||
This class extends asynMotorController by some features used in SINQ. See the
|
||||
README.md for details.
|
||||
|
||||
Code lifted from Torsten Boegershausens ESS code.
|
||||
|
||||
Mark Koennecke, March 2017
|
||||
Stefan Mathis, November 2024
|
||||
*/
|
||||
|
||||
#ifndef __sinqController
|
||||
#define __sinqController
|
||||
#ifndef sinqController_H
|
||||
#define sinqController_H
|
||||
|
||||
// The EPICS libaries do not follow -Weffc++
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Weffc++"
|
||||
|
||||
#include "asynMotorController.h"
|
||||
#include "msgPrintControl.h"
|
||||
#include <initHooks.h>
|
||||
#include <macros.h>
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <memory>
|
||||
|
||||
#define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER"
|
||||
#define motorMessageTextString "MOTOR_MESSAGE_TEXT"
|
||||
#define IncrementalEncoder "incremental"
|
||||
#define AbsoluteEncoder "absolute"
|
||||
#define NoEncoder "none"
|
||||
|
||||
class epicsShareClass sinqController : public asynMotorController {
|
||||
struct HIDDEN sinqControllerImpl;
|
||||
|
||||
class HIDDEN sinqController : public asynMotorController {
|
||||
public:
|
||||
sinqController(const char *portName, const char *SINQPortName, int numAxes,
|
||||
const int &extraParams = 2);
|
||||
|
||||
friend class sinqAxis;
|
||||
/**
|
||||
* @brief Construct a new sinqController object
|
||||
*
|
||||
* @param portName Controller can be found by findAsynPortDriver
|
||||
* with this name
|
||||
* @param ipPortConfigName IP adress and port configuration of the
|
||||
* controller unit, used to connect via pasynOctetSyncIO->connect
|
||||
* @param numAxes Pointers to the axes are stored in the array
|
||||
pAxes_ which has the length specified here. When getting an axis, the
|
||||
`getAxis` function indexes into this array. A length of 8 would therefore
|
||||
mean that the axis slots 0 to 7 are available. However, in order to keep the
|
||||
axis enumeration identical to that of the hardware, we start counting the
|
||||
axes with 1 and end at 8. Therefore, an offset of 1 is added when forwarding
|
||||
this number to asynMotorController.
|
||||
* @param movingPollPeriod Time between polls when moving (in seconds)
|
||||
* @param idlePollPeriod Time between polls when not moving (in
|
||||
seconds)
|
||||
* @param extraParams Number of extra parameter library entries
|
||||
* created in a concrete driver implementation
|
||||
*/
|
||||
sinqController(const char *portName, const char *ipPortConfigName,
|
||||
int numAxes, double movingPollPeriod, double idlePollPeriod,
|
||||
int numExtraParams);
|
||||
|
||||
/**
|
||||
If accessing the parameter library failed (return status != asynSuccess),
|
||||
calling this function writes a standardized message to both the IOC shell
|
||||
and the motor message text PV. It then returns the input status.
|
||||
* @brief Destroy the sinqController object
|
||||
*
|
||||
* In general, there is no real memory cleanup strategy in asynMotor,
|
||||
* because objects are expected to be alive for the entire lifetime of the
|
||||
* IOC. We just clean up the allocated axes array here.
|
||||
*/
|
||||
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
|
||||
*
|
||||
* The function is overloaded to allow enabling / disabling the motor.
|
||||
*
|
||||
* @param pasynUser Specify the axis via the asynUser
|
||||
* @param value New value
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
|
||||
|
||||
/**
|
||||
* @brief Overloaded function of asynMotorController
|
||||
*
|
||||
* The function is overloaded to get readback values for the enabling /
|
||||
* disabling status.
|
||||
*
|
||||
* @param pasynUser Specify the axis via the asynUser
|
||||
* @param value Read-out value
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value);
|
||||
|
||||
/**
|
||||
* @brief Error handling in case accessing the parameter library failed.
|
||||
*
|
||||
* If accessing the parameter library failed (return status !=
|
||||
asynSuccess), calling this function writes a standardized message to both
|
||||
the IOC shell and the motorMessageText PV. It then returns the input
|
||||
status.
|
||||
*
|
||||
* @param status Status of the failed parameter library access
|
||||
* @param parameter Name of the parameter, used to create the
|
||||
error messages.
|
||||
* @param functionName Name of the caller function. It is recommended
|
||||
to use a macro, e.g. __func__ or __PRETTY_FUNCTION__.
|
||||
* @param line Source code line where this function is
|
||||
called. It is recommended to use a macro, e.g. __LINE__.
|
||||
* @return asynStatus Returns input status.
|
||||
*/
|
||||
asynStatus paramLibAccessFailed(asynStatus status, const char *parameter,
|
||||
const char *functionName, int lineNumber);
|
||||
int axisNo, const char *functionName,
|
||||
int line);
|
||||
|
||||
/**
|
||||
This function writes a standardized message to both the IOC shell and
|
||||
* @brief Error handling in case parsing a command response failed.
|
||||
*
|
||||
* This function writes a standardized message to both the IOC shell and
|
||||
the motor message text PV in case parsing a response (e.g. via sscanf)
|
||||
failed. It always returns asynError.
|
||||
failed. It always returns asynError. This is convenience feature so the
|
||||
function call can be used as a return value in an error handling branch.
|
||||
*
|
||||
* @param command Command which led to the unparseable message
|
||||
* @param response Response which wasn't parseable
|
||||
* @param axisNo_ Axis where the problem occurred
|
||||
* @param functionName Name of the caller function. It is recommended
|
||||
to use a macro, e.g. __func__ or __PRETTY_FUNCTION__.
|
||||
* @param line Source code line where this function is
|
||||
called. It is recommended to use a macro, e.g. __LINE__.
|
||||
* @return asynStatus Returns asynError.
|
||||
*/
|
||||
asynStatus errMsgCouldNotParseResponse(const char *command,
|
||||
const char *response, int axisNo_,
|
||||
const char *functionName,
|
||||
int lineNumber);
|
||||
asynStatus couldNotParseResponse(const char *command, const char *response,
|
||||
int axisNo, const char *functionName,
|
||||
int line);
|
||||
|
||||
/**
|
||||
Convert an asynStatus into a descriptive string. This string can then e.g.
|
||||
be used to create debugging messages.
|
||||
* @brief Convert an asynStatus into a descriptive string.
|
||||
*
|
||||
* @param status Status which should be converted to a string.
|
||||
* @return const char*
|
||||
*/
|
||||
const char *stringifyAsynStatus(asynStatus status);
|
||||
|
||||
protected:
|
||||
asynUser *lowLevelPortUser_;
|
||||
int motorMessageText_;
|
||||
/**
|
||||
* @brief This function should be called when a communication timeout
|
||||
occured. It calculates the frequency of communication timeout events and
|
||||
creates an error message, if an threshold has been exceeded.
|
||||
|
||||
Occasionally, communication timeouts between the IOC and the motor
|
||||
controller may happen, usually because the controller takes too long to
|
||||
respond. If this happens infrequently, this is not a problem. However, if it
|
||||
happens very often, this may indicate a network problem and must therefore
|
||||
be forwarded to the user. This is checked by calculating the moving average
|
||||
of events and comparing it to a threshhold. Both the threshold and the time
|
||||
window for the moving average can be configured in the IOC via the function
|
||||
setThresholdCom.
|
||||
|
||||
This function exists in two variants: Either the error message can be
|
||||
written into a buffer provided by the caller or it written directly into the
|
||||
parameter library of the provided axis.
|
||||
|
||||
* @param axis Axis to which the error message is sent
|
||||
*
|
||||
* @return asynStatus asynError, if the threshold has been
|
||||
exceeded, asynSuccess otherwise
|
||||
*/
|
||||
virtual asynStatus checkComTimeoutWatchdog(class sinqAxis *axis);
|
||||
|
||||
/**
|
||||
* @brief See documentation of checkComTimeoutWatchdog(sinqAxis * axis)
|
||||
*
|
||||
* @param userMessage Buffer for the user message
|
||||
* @param userMessageSize Buffer size in chars
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus checkComTimeoutWatchdog(int axisNo, char *motorMessage,
|
||||
size_t motorMessageSize);
|
||||
|
||||
/**
|
||||
* @brief Set the threshold for the communication timeout mechanism
|
||||
*
|
||||
* @param comTimeoutWindow Size of the time window used to calculate
|
||||
* the moving average of timeout events. Set this value to 0 to deactivate
|
||||
* the watchdog.
|
||||
* @param maxNumberTimeouts Maximum number of timeouts which may occur
|
||||
* within the time window before the watchdog is triggered.
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus setThresholdComTimeout(time_t comTimeoutWindow,
|
||||
size_t maxNumberTimeouts);
|
||||
|
||||
/**
|
||||
* @brief Inform the user, if the number of timeouts exceeds the threshold
|
||||
* specified with `setMaxSubsequentTimeouts`.
|
||||
*
|
||||
* @param timeoutNo Number of subsequent timeouts which already
|
||||
* happened.
|
||||
* @param axis
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus checkMaxSubsequentTimeouts(int timeoutNo,
|
||||
class sinqAxis *axis);
|
||||
|
||||
/**
|
||||
* @brief See documentation of `checkMaxSubsequentTimeouts(sinqAxis * axis)`
|
||||
*
|
||||
* @param userMessage Buffer for the user message
|
||||
* @param userMessageSize Buffer size in chars
|
||||
* @return asynStatus
|
||||
*/
|
||||
virtual asynStatus checkMaxSubsequentTimeouts(int timeoutNo, int axisNo,
|
||||
char *motorMessage,
|
||||
size_t motorMessageSize);
|
||||
|
||||
/**
|
||||
* @brief Set the maximum number of subsequent timeouts before the user is
|
||||
* informed.
|
||||
*
|
||||
* @param maxSubsequentTimeouts
|
||||
* @return asynStatus
|
||||
*/
|
||||
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();
|
||||
|
||||
/**
|
||||
* @brief Get the axis object
|
||||
*
|
||||
* @param pasynUser Specify the axis via the asynUser
|
||||
* @return sinqAxis* If no axis could be found, this is a nullptr
|
||||
*/
|
||||
sinqAxis *getSinqAxis(asynUser *pasynUser);
|
||||
|
||||
/**
|
||||
* @brief Get the axis object
|
||||
*
|
||||
* @param axisNo Specify the axis via its index
|
||||
* @return sinqAxis* If no axis could be found, this is a nullptr
|
||||
*/
|
||||
sinqAxis *getSinqAxis(int axisNo);
|
||||
|
||||
// =========================================================================
|
||||
// Public getters for protected members
|
||||
|
||||
// Accessors for double parameters
|
||||
int motorMoveRel() { return motorMoveRel_; }
|
||||
int motorMoveAbs() { return motorMoveAbs_; }
|
||||
int motorMoveVel() { return motorMoveVel_; }
|
||||
int motorHome() { return motorHome_; }
|
||||
int motorStop() { return motorStop_; }
|
||||
int motorVelocity() { return motorVelocity_; }
|
||||
int motorVelBase() { return motorVelBase_; }
|
||||
int motorAccel() { return motorAccel_; }
|
||||
int motorPosition() { return motorPosition_; }
|
||||
int motorEncoderPosition() { return motorEncoderPosition_; }
|
||||
int motorDeferMoves() { return motorDeferMoves_; }
|
||||
int motorMoveToHome() { return motorMoveToHome_; }
|
||||
int motorResolution() { return motorResolution_; }
|
||||
int motorEncoderRatio() { return motorEncoderRatio_; }
|
||||
int motorPGain() { return motorPGain_; }
|
||||
int motorIGain() { return motorIGain_; }
|
||||
int motorDGain() { return motorDGain_; }
|
||||
int motorHighLimit() { return motorHighLimit_; }
|
||||
int motorLowLimit() { return motorLowLimit_; }
|
||||
int motorClosedLoop() { return motorClosedLoop_; }
|
||||
int motorPowerAutoOnOff() { return motorPowerAutoOnOff_; }
|
||||
int motorPowerOnDelay() { return motorPowerOnDelay_; }
|
||||
int motorPowerOffDelay() { return motorPowerOffDelay_; }
|
||||
int motorPowerOffFraction() { return motorPowerOffFraction_; }
|
||||
int motorPostMoveDelay() { return motorPostMoveDelay_; }
|
||||
int motorStatus() { return motorStatus_; }
|
||||
int motorUpdateStatus() { return motorUpdateStatus_; }
|
||||
|
||||
// Accessors for status bits (integers)
|
||||
int motorStatusDirection() { return motorStatusDirection_; }
|
||||
int motorStatusDone() { return motorStatusDone_; }
|
||||
int motorStatusHighLimit() { return motorStatusHighLimit_; }
|
||||
int motorStatusAtHome() { return motorStatusAtHome_; }
|
||||
int motorStatusSlip() { return motorStatusSlip_; }
|
||||
int motorStatusPowerOn() { return motorStatusPowerOn_; }
|
||||
int motorStatusFollowingError() { return motorStatusFollowingError_; }
|
||||
int motorStatusHome() { return motorStatusHome_; }
|
||||
int motorStatusHasEncoder() { return motorStatusHasEncoder_; }
|
||||
int motorStatusProblem() { return motorStatusProblem_; }
|
||||
int motorStatusMoving() { return motorStatusMoving_; }
|
||||
int motorStatusGainSupport() { return motorStatusGainSupport_; }
|
||||
int motorStatusCommsError() { return motorStatusCommsError_; }
|
||||
int motorStatusLowLimit() { return motorStatusLowLimit_; }
|
||||
int motorStatusHomed() { return motorStatusHomed_; }
|
||||
|
||||
// Parameters for passing additional motor record information to the driver
|
||||
int motorRecResolution() { return motorRecResolution_; }
|
||||
int motorRecDirection() { return motorRecDirection_; }
|
||||
int motorRecOffset() { return motorRecOffset_; }
|
||||
|
||||
// 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_; }
|
||||
double idlePollPeriod() { return idlePollPeriod_; }
|
||||
double movingPollPeriod() { return movingPollPeriod_; }
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the asynUser of the controller
|
||||
*
|
||||
* @return asynUser*
|
||||
*/
|
||||
asynUser *pasynUser() { return pasynUserSelf; }
|
||||
|
||||
/**
|
||||
* @brief Return a pointer to the low-level octet (string) IP Port
|
||||
*
|
||||
* @return asynUser*
|
||||
*/
|
||||
asynUser *pasynOctetSyncIOipPort();
|
||||
|
||||
/**
|
||||
* @brief Overloaded version of `asynController::poll` which decreases
|
||||
* `outstandingForcedFastPolls` and then defers to the base method
|
||||
*/
|
||||
asynStatus poll();
|
||||
|
||||
/**
|
||||
* @brief Overloaded version of `asynController::wakeupPoller` which
|
||||
* initializes the `outstandingForcedFastPolls` variable and then defers to
|
||||
* the base class method.
|
||||
*
|
||||
* The `wakePoller` function of the base class `asynController` sends a
|
||||
* signal to the poller thread which forces the latter to perform a number
|
||||
* of fast / busy polls with the busy poll period regardless of whether the
|
||||
* motor is moving or not. The number of polls is specified by
|
||||
* "forcedFastPolls()" and can be set with `setForcedFastPolls()`.
|
||||
*
|
||||
* @return asynStatus
|
||||
*/
|
||||
asynStatus wakeupPoller();
|
||||
|
||||
/**
|
||||
* @brief Set the number of forced fast polls which should be performed when
|
||||
* `wakeupPoller` is called.
|
||||
*
|
||||
* @param forcedFastPolls
|
||||
*/
|
||||
void setForcedFastPolls(int forcedFastPolls) {
|
||||
forcedFastPolls_ = forcedFastPolls;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read the number of forced fast polls currently specified
|
||||
*
|
||||
*/
|
||||
int forcedFastPolls() { return forcedFastPolls_; }
|
||||
|
||||
/**
|
||||
* @brief Read the number of outstanding forced fast polls currently
|
||||
* specified
|
||||
*
|
||||
*/
|
||||
int outstandingForcedFastPolls();
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
// =========================================================================
|
||||
|
||||
private:
|
||||
std::unique_ptr<sinqControllerImpl> pSinqC_;
|
||||
static void epicsInithookFunction(initHookState iState);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
7
src/sinqMotor.dbd
Normal file
7
src/sinqMotor.dbd
Normal file
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#---------------------------------------------
|
||||
# SINQ specific DB definitions
|
||||
#---------------------------------------------
|
||||
registrar(sinqControllerRegister)
|
||||
registrar(sinqAxisRegister)
|
||||
Reference in New Issue
Block a user