Merge branch 'add_fake_library' into 'stable-1.6'
Add fake library See merge request etherlab.org/ethercat!136
This commit is contained in:
commit
419f62161b
|
|
@ -33,6 +33,7 @@ ethercat.spec
|
||||||
examples/dc_user/ec_dc_user_example
|
examples/dc_user/ec_dc_user_example
|
||||||
examples/user/ec_user_example
|
examples/user/ec_user_example
|
||||||
examples/user/build/
|
examples/user/build/
|
||||||
|
fake_lib/libfakeethercat.la
|
||||||
lib/*.cmake
|
lib/*.cmake
|
||||||
lib/libethercat.la
|
lib/libethercat.la
|
||||||
lib/libethercat.pc
|
lib/libethercat.pc
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
image:
|
image:
|
||||||
registry.gitlab.com/etherlab.org/build-container-factory/leap-15.3:linux-syms
|
registry.gitlab.com/etherlab.org/build-container-factory/leap-15.4:linux-syms
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
|
|
@ -60,14 +60,14 @@ build:
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./bootstrap
|
- ./bootstrap
|
||||||
- ./configure --with-linux-dir=/usr/src/linux-obj/$(uname -i)/default --enable-tty --with-devices=2 --enable-ccat
|
- ./configure --with-linux-dir=/usr/src/linux-obj/$(uname -i)/default --enable-tty --with-devices=2 --enable-ccat --enable-fakeuserlib
|
||||||
- make -j8 all modules
|
- make -j8 all modules
|
||||||
- make -C lib symbol-version-check
|
- make -C lib symbol-version-check
|
||||||
- make DISTCHECK_CONFIGURE_FLAGS="--with-linux-dir=/usr/src/linux-obj/$(uname -i)/default" distcheck
|
- make DISTCHECK_CONFIGURE_FLAGS="--with-linux-dir=/usr/src/linux-obj/$(uname -i)/default" distcheck
|
||||||
- make dist
|
- make dist
|
||||||
- mkdir test_dist && cd test_dist && tar xzf ../ethercat-*.tar.gz && cd ethercat-*/
|
- mkdir test_dist && cd test_dist && tar xzf ../ethercat-*.tar.gz && cd ethercat-*/
|
||||||
- mkdir build && cd build
|
- mkdir build && cd build
|
||||||
- ../configure --with-linux-dir=/usr/src/linux-obj/$(uname -i)/default --disable-8139too --enable-tty --with-devices=2 --enable-ccat
|
- ../configure --with-linux-dir=/usr/src/linux-obj/$(uname -i)/default --disable-8139too --enable-tty --with-devices=2 --enable-ccat --enable-fakeuserlib
|
||||||
- make -j8 all modules
|
- make -j8 all modules
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
|
|
@ -124,8 +124,11 @@ doxygen:
|
||||||
# It must be named 'pdf' and must produce a 'pdf' directory as an artifact.
|
# It must be named 'pdf' and must produce a 'pdf' directory as an artifact.
|
||||||
# All PDF files in this directory will be collected.
|
# All PDF files in this directory will be collected.
|
||||||
pdf:
|
pdf:
|
||||||
|
needs:
|
||||||
|
- job: commands
|
||||||
|
artifacts: true
|
||||||
stage: doc
|
stage: doc
|
||||||
image: registry.gitlab.com/etherlab.org/build-container-factory/leap-15.3:texlive
|
image: registry.gitlab.com/etherlab.org/build-container-factory/leap-15.4:texlive
|
||||||
script:
|
script:
|
||||||
- cd documentation
|
- cd documentation
|
||||||
- git show -s --format="\def\revision{%h}\def\gitversion{%(describe)}\def\gittag{%(describe:abbrev=0)}\def\gitauthor{%an}\def\isodate#1-#2-#3x{\day=#3 \month=#2 \year=#1}\isodate %csx" HEAD > git.tex
|
- git show -s --format="\def\revision{%h}\def\gitversion{%(describe)}\def\gittag{%(describe:abbrev=0)}\def\gitauthor{%an}\def\isodate#1-#2-#3x{\day=#3 \month=#2 \year=#1}\isodate %csx" HEAD > git.tex
|
||||||
|
|
|
||||||
2947
Doxyfile.in
2947
Doxyfile.in
File diff suppressed because it is too large
Load Diff
|
|
@ -47,6 +47,11 @@ SUBDIRS += \
|
||||||
examples
|
examples
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if ENABLE_FAKEUSERLIB
|
||||||
|
SUBDIRS += \
|
||||||
|
fake_lib
|
||||||
|
endif
|
||||||
|
|
||||||
if ENABLE_TTY
|
if ENABLE_TTY
|
||||||
SUBDIRS += tty
|
SUBDIRS += tty
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
4
NEWS
4
NEWS
|
|
@ -4,6 +4,10 @@ vim: spelllang=en spell tw=78
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Changes in 1.6.1:
|
||||||
|
|
||||||
|
* Added libfakeethercat to simulate Process Data of EtherCAT Slaves.
|
||||||
|
|
||||||
Changes in 1.6.0:
|
Changes in 1.6.0:
|
||||||
|
|
||||||
* Added all native Ethernet drivers for kernels 5.14 and 6.1.
|
* Added all native Ethernet drivers for kernels 5.14 and 6.1.
|
||||||
|
|
|
||||||
15
README.md
15
README.md
|
|
@ -15,7 +15,7 @@ Contents:
|
||||||
This is an open-source EtherCAT master implementation for Linux 2.6 or newer.
|
This is an open-source EtherCAT master implementation for Linux 2.6 or newer.
|
||||||
|
|
||||||
See the [features file](FEATURES.md) for a list of features. For more
|
See the [features file](FEATURES.md) for a list of features. For more
|
||||||
information, see http://etherlab.org/en/ethercat.
|
information, see https://etherlab.org/ethercat.
|
||||||
|
|
||||||
or contact
|
or contact
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ make doc
|
||||||
```
|
```
|
||||||
|
|
||||||
An up-to-date Doxygen output can be found on
|
An up-to-date Doxygen output can be found on
|
||||||
[docs.etherlab.org](https://docs.etherlab.org/ethercat/1.5/doxygen/index.html).
|
[docs.etherlab.org](https://docs.etherlab.org/ethercat/1.6/doxygen/index.html).
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
|
|
@ -65,13 +65,20 @@ the EtherCAT master.
|
||||||
|
|
||||||
## Hardware requirements
|
## Hardware requirements
|
||||||
|
|
||||||
A table of supported hardware can be found at
|
A table of supported hardware can be found at:
|
||||||
http://etherlab.org/en/ethercat/hardware.php.
|
https://docs.etherlab.org/ethercat/1.6/doxygen/devicedrivers.html
|
||||||
|
|
||||||
# Building and installing
|
# Building and installing
|
||||||
|
|
||||||
See the [install file](INSTALL.md).
|
See the [install file](INSTALL.md).
|
||||||
|
|
||||||
|
# Dry-run and Field Simulation
|
||||||
|
|
||||||
|
A limited set of the userspace API is available in `libfakeethercat`,
|
||||||
|
a library which can be used to run an userspace application
|
||||||
|
without an EtherCAT master or with emulated EtherCAT slaves.
|
||||||
|
Please find some details [here](fake_lib/README.md).
|
||||||
|
|
||||||
# Realtime and Tuning
|
# Realtime and Tuning
|
||||||
|
|
||||||
Realtime patches for the Linux kernel are supported, but not required. The
|
Realtime patches for the Linux kernel are supported, but not required. The
|
||||||
|
|
|
||||||
34
configure.ac
34
configure.ac
|
|
@ -31,7 +31,7 @@ AC_PREREQ(2.59)
|
||||||
# - Update version number below
|
# - Update version number below
|
||||||
# - make dist-bzip2
|
# - make dist-bzip2
|
||||||
#
|
#
|
||||||
AC_INIT([ethercat],[1.6.0],[fp@igh.de])
|
AC_INIT([ethercat],[1.6.1],[fp@igh.de])
|
||||||
AC_CONFIG_AUX_DIR([autoconf])
|
AC_CONFIG_AUX_DIR([autoconf])
|
||||||
AM_INIT_AUTOMAKE([-Wall -Wno-override -Werror dist-bzip2 subdir-objects foreign])
|
AM_INIT_AUTOMAKE([-Wall -Wno-override -Werror dist-bzip2 subdir-objects foreign])
|
||||||
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
||||||
|
|
@ -1168,6 +1168,37 @@ fi
|
||||||
|
|
||||||
AM_CONDITIONAL(ENABLE_USERLIB, test "x$userlib" = "x1")
|
AM_CONDITIONAL(ENABLE_USERLIB, test "x$userlib" = "x1")
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Fake Userspace library generation
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
AC_MSG_CHECKING([whether to build the fake userspace library])
|
||||||
|
|
||||||
|
AC_ARG_ENABLE([fakeuserlib],
|
||||||
|
AS_HELP_STRING([--enable-fakeuserlib],
|
||||||
|
[Generation of the userspace library (default: no)]),
|
||||||
|
[
|
||||||
|
case "${enableval}" in
|
||||||
|
yes) fakeuserlib=1
|
||||||
|
;;
|
||||||
|
no) fakeuserlib=0
|
||||||
|
;;
|
||||||
|
*) AC_MSG_ERROR([Invalid value for --enable-fakeuserlib])
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
],
|
||||||
|
[fakeuserlib=0]
|
||||||
|
)
|
||||||
|
|
||||||
|
if test "x${fakeuserlib}" = "x1"; then
|
||||||
|
AC_MSG_RESULT([yes])
|
||||||
|
PKG_CHECK_MODULES([RTIPC], [librtipc])
|
||||||
|
else
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
fi
|
||||||
|
|
||||||
|
AM_CONDITIONAL(ENABLE_FAKEUSERLIB, test "x$fakeuserlib" = "x1")
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
# TTY driver
|
# TTY driver
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
@ -1389,6 +1420,7 @@ AC_CONFIG_FILES([
|
||||||
examples/user/Makefile
|
examples/user/Makefile
|
||||||
examples/xenomai/Makefile
|
examples/xenomai/Makefile
|
||||||
examples/xenomai_posix/Makefile
|
examples/xenomai_posix/Makefile
|
||||||
|
fake_lib/Makefile
|
||||||
include/Makefile
|
include/Makefile
|
||||||
lib/Makefile
|
lib/Makefile
|
||||||
lib/libethercat.pc
|
lib/libethercat.pc
|
||||||
|
|
|
||||||
|
|
@ -351,7 +351,7 @@ Public License (GPL \cite{gpl})\index{GPL}, version 2. Other developers, that
|
||||||
want to use EtherCAT with Linux systems, are invited to use the master code or
|
want to use EtherCAT with Linux systems, are invited to use the master code or
|
||||||
even participate on development.
|
even participate on development.
|
||||||
|
|
||||||
To allow static linking of userspace application against the master's
|
To allow dynamic linking of userspace application against the master's
|
||||||
application interface (see \autoref{chap:api}), the userspace library (see
|
application interface (see \autoref{chap:api}), the userspace library (see
|
||||||
\autoref{sec:userlib}) is licensed under the terms and conditions of the GNU
|
\autoref{sec:userlib}) is licensed under the terms and conditions of the GNU
|
||||||
Lesser General Public License (LGPL \cite{lgpl})\index{LGPL}, version 2.1.
|
Lesser General Public License (LGPL \cite{lgpl})\index{LGPL}, version 2.1.
|
||||||
|
|
@ -962,8 +962,8 @@ be found.
|
||||||
The application interface of the EtherCAT master is defined in the header file
|
The application interface of the EtherCAT master is defined in the header file
|
||||||
\textit{include/ecrt.h} (acronym for ``EtherCAT Real-Time'') which is listed
|
\textit{include/ecrt.h} (acronym for ``EtherCAT Real-Time'') which is listed
|
||||||
in this section. The calling conventions of all methods are documented in the
|
in this section. The calling conventions of all methods are documented in the
|
||||||
comments of this header. There is also a Doxygen-generated online version at
|
comments of this header. There is also a Doxygen-generated \cite{doxygen}
|
||||||
\url{https://docs.etherlab.org}.
|
online version at \url{https://docs.etherlab.org}.
|
||||||
|
|
||||||
\lstinputlisting[
|
\lstinputlisting[
|
||||||
basicstyle=\ttfamily\scriptsize,
|
basicstyle=\ttfamily\scriptsize,
|
||||||
|
|
@ -2709,6 +2709,10 @@ installation prefix as \textit{libethercat.a} (for static linking),
|
||||||
\textit{libethercat.la} (for the use with \textit{libtool}) and
|
\textit{libethercat.la} (for the use with \textit{libtool}) and
|
||||||
\textit{libethercat.so} (for dynamic linking).
|
\textit{libethercat.so} (for dynamic linking).
|
||||||
|
|
||||||
|
For running an application without actual EtherCAT hardware or for simulation
|
||||||
|
purposes, there is a special library called \textit{libfakeethercat} (see
|
||||||
|
\autoref{sec:fakelib}).
|
||||||
|
|
||||||
\subsection{Using the Library}
|
\subsection{Using the Library}
|
||||||
|
|
||||||
The application interface header \textit{ecrt.h} (see \autoref{sec:ecrt}) can
|
The application interface header \textit{ecrt.h} (see \autoref{sec:ecrt}) can
|
||||||
|
|
@ -2750,6 +2754,9 @@ gcc -static ectest.c -o ectest -I/opt/etherlab/include \
|
||||||
/opt/etherlab/lib/libethercat.a
|
/opt/etherlab/lib/libethercat.a
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Please keep in mind, that your application has to be licensed under GPLv2
|
||||||
|
then, because the LGPL does only allow dynamic linking.
|
||||||
|
|
||||||
\subsection{Implementation}
|
\subsection{Implementation}
|
||||||
\label{sec:userimp}
|
\label{sec:userimp}
|
||||||
|
|
||||||
|
|
@ -2836,6 +2843,33 @@ The test results show, that for this configuration, the userspace API causes
|
||||||
about \unit{1}{\micro\second} additional delay for each function, compared to
|
about \unit{1}{\micro\second} additional delay for each function, compared to
|
||||||
the kernel API.
|
the kernel API.
|
||||||
|
|
||||||
|
\subsection{Simulation / Fake Library}
|
||||||
|
\label{sec:fakelib}
|
||||||
|
|
||||||
|
Sometimes is is handy to run your EtherCAT realtime application without an
|
||||||
|
actual EtherCAT network connected, for example for test purposes.
|
||||||
|
Though it is possible to spin up an EtherCAT master and to connect it to a
|
||||||
|
loopback device, this step is not always wanted.
|
||||||
|
|
||||||
|
The EtherCAT master (since version 1.6.1) comes with a library
|
||||||
|
\textit{libfakeethercat} that comes with a reasonable subset of the EtherCAT
|
||||||
|
application interface (see \autoref{chap:api}).
|
||||||
|
|
||||||
|
The \texttt{ecrt} method implementation in the fake library will just accept
|
||||||
|
your input and behave as if everything would be fine. Without further steps,
|
||||||
|
the process data will be all-zero then.
|
||||||
|
|
||||||
|
As a special feature, the \textit{libfakeethercat} will create RtIPC
|
||||||
|
\cite{rtipc} endpoints for registered PDO entries to enable a simulation
|
||||||
|
interface. Another application that either uses RtIPC directly or another
|
||||||
|
(inverted) instance of \textit{libfakeethercat} will then connect to these
|
||||||
|
endpoints and thus create the possibility to provide simulated values to your
|
||||||
|
pristine application.
|
||||||
|
|
||||||
|
The fake library functions an usage is documented in Doxygen~\cite{doxygen}
|
||||||
|
and the most recent version can be found online:
|
||||||
|
\url{https://docs.etherlab.org/ethercat/1.6/doxygen/libfakeethercat.html}
|
||||||
|
|
||||||
%------------------------------------------------------------------------------
|
%------------------------------------------------------------------------------
|
||||||
|
|
||||||
\section{RTDM Interface}
|
\section{RTDM Interface}
|
||||||
|
|
@ -3547,6 +3581,9 @@ Applications.
|
||||||
\url{http://svn.gna.org/svn/xenomai/tags/v2.4.0/doc/nodist/pdf/RTDM-and-Applications.pdf},
|
\url{http://svn.gna.org/svn/xenomai/tags/v2.4.0/doc/nodist/pdf/RTDM-and-Applications.pdf},
|
||||||
2013.
|
2013.
|
||||||
|
|
||||||
|
\bibitem{rtipc} Real-Time Inter-Process-Communication library. Part of the
|
||||||
|
EtherLab toolkit. \url{https://gitlab.com/etherlab.org/rtipc}, 2024.
|
||||||
|
|
||||||
\end{thebibliography}
|
\end{thebibliography}
|
||||||
|
|
||||||
\printnomenclature
|
\printnomenclature
|
||||||
|
|
|
||||||
|
|
@ -3440,7 +3440,7 @@ Il y a plusieurs mani\`eres d'obtenir le logiciel du ma\^itre:
|
||||||
|
|
||||||
\item Une version officielle (par exemple \masterversion) peut \^etre
|
\item Une version officielle (par exemple \masterversion) peut \^etre
|
||||||
t\'el\'echarg\'ee depuis le site web du
|
t\'el\'echarg\'ee depuis le site web du
|
||||||
ma\^itre\footnote{\url{http://etherlab.org/en/ethercat/index.php}}
|
ma\^itre\footnote{\url{https://etherlab.org/ethercat}}
|
||||||
dans le projet EtherLab~\cite{etherlab} sous forme d'archive tar.
|
dans le projet EtherLab~\cite{etherlab} sous forme d'archive tar.
|
||||||
|
|
||||||
\item La r\'evision de d\'eveloppement la plus r\'ecente (mais aussi
|
\item La r\'evision de d\'eveloppement la plus r\'ecente (mais aussi
|
||||||
|
|
@ -3793,7 +3793,7 @@ KERNEL=="EtherCAT[0-9]*", MODE="0664", GROUP="users"
|
||||||
|
|
||||||
\bibitem{etherlab} Ingenieurgemeinschaft IgH: EtherLab -- Open Source Toolkit
|
\bibitem{etherlab} Ingenieurgemeinschaft IgH: EtherLab -- Open Source Toolkit
|
||||||
for rapid realtime code generation under Linux with Simulink/RTW and EtherCAT
|
for rapid realtime code generation under Linux with Simulink/RTW and EtherCAT
|
||||||
technology. \url{http://etherlab.org/en}, 2008.
|
technology. \url{https://etherlab.org}, 2024.
|
||||||
|
|
||||||
\bibitem{dlspec} IEC 61158-4-12: Data-link Protocol Specification.
|
\bibitem{dlspec} IEC 61158-4-12: Data-link Protocol Specification.
|
||||||
International Electrotechnical Commission (IEC), 2005.
|
International Electrotechnical Commission (IEC), 2005.
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ Version: @VERSION@
|
||||||
Release: 1
|
Release: 1
|
||||||
|
|
||||||
License: GPL
|
License: GPL
|
||||||
URL: http://etherlab.org/en/ethercat
|
URL: https://etherlab.org/ethercat
|
||||||
|
|
||||||
Provides: @PACKAGE@
|
Provides: @PACKAGE@
|
||||||
Source: %{name}-%{version}.tar.bz2
|
Source: %{name}-%{version}.tar.bz2
|
||||||
|
|
@ -44,7 +44,7 @@ Group: EtherLab
|
||||||
%description
|
%description
|
||||||
This is an open-source EtherCAT master implementation for Linux 2.6. See the
|
This is an open-source EtherCAT master implementation for Linux 2.6. See the
|
||||||
FEATURES file for a list of features. For more information, see
|
FEATURES file for a list of features. For more information, see
|
||||||
http://etherlab.org/en/ethercat.
|
https://etherlab.org/ethercat.
|
||||||
|
|
||||||
%kernel_module_package
|
%kernel_module_package
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ Group: EtherLab
|
||||||
%description devel
|
%description devel
|
||||||
This is an open-source EtherCAT master implementation for Linux 2.6. See the
|
This is an open-source EtherCAT master implementation for Linux 2.6. See the
|
||||||
FEATURES file for a list of features. For more information, see
|
FEATURES file for a list of features. For more information, see
|
||||||
http://etherlab.org/en/ethercat.
|
https://etherlab.org/ethercat.
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Copyright (C) 2006-2024 Florian Pose, Ingenieurgemeinschaft IgH
|
||||||
|
#
|
||||||
|
# This file is part of the IgH EtherCAT master userspace library.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT master userspace library is free software; you can
|
||||||
|
# redistribute it and/or modify it under the terms of the GNU Lesser General
|
||||||
|
# Public License as published by the Free Software Foundation; version 2.1 of
|
||||||
|
# the License.
|
||||||
|
#
|
||||||
|
# The IgH EtherCAT master userspace library 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 Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with the IgH EtherCAT master userspace library. If not, see
|
||||||
|
# <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
lib_LTLIBRARIES = libfakeethercat.la
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
libfakeethercat_la_SOURCES = \
|
||||||
|
fakeethercat.cpp
|
||||||
|
|
||||||
|
noinst_HEADERS = \
|
||||||
|
fakeethercat.h
|
||||||
|
|
||||||
|
libfakeethercat_la_CXXFLAGS = \
|
||||||
|
-fno-strict-aliasing \
|
||||||
|
-Wall \
|
||||||
|
-I$(top_srcdir)/include \
|
||||||
|
-Dethercat_EXPORTS \
|
||||||
|
-fvisibility=hidden \
|
||||||
|
$(RTIPC_CFLAGS)
|
||||||
|
|
||||||
|
libfakeethercat_la_LDFLAGS = -version-info 3:0:2 \
|
||||||
|
$(RTIPC_LIBS) \
|
||||||
|
-Wl,--version-script=$(srcdir)/../lib/libethercat.map \
|
||||||
|
-fvisibility=hidden
|
||||||
|
|
||||||
|
libfakeethercat_la_DEPENDENCIES = ../lib/libethercat.map
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
FakeEtherCAT Library {#libfakeethercat}
|
||||||
|
====================
|
||||||
|
|
||||||
|
Libfakeethercat is a userspace library which has the same API as
|
||||||
|
the EtherCAT master interface library libethercat.
|
||||||
|
Libfakeethercat can be used to spin up your RT application in a dry-run mode,
|
||||||
|
without any master configured or slaves attached.
|
||||||
|
Furthermore, it is possible to emulate EtherCAT slaves on process data level
|
||||||
|
by running two applications back to back.
|
||||||
|
|
||||||
|
## Supported features
|
||||||
|
|
||||||
|
Currently, only a very limited subset of libethercat functionality is supported:
|
||||||
|
|
||||||
|
- Creating master and domain instances
|
||||||
|
- Activating master, `send`/`receive`.
|
||||||
|
- Processing and queuing domains
|
||||||
|
- Configuring PDOs
|
||||||
|
- Configuring SDOs using `ecrt_slave_config_sdo*`
|
||||||
|
|
||||||
|
The SDO config does not do anything,
|
||||||
|
but when activating the master the SDO config will
|
||||||
|
be dumped into a JSON file.
|
||||||
|
|
||||||
|
ecrt_master_state() and ecrt_domain_state() both return states as if
|
||||||
|
the bus works without errors.
|
||||||
|
So currenty, a bus error cannot be simulated.
|
||||||
|
|
||||||
|
## How to build
|
||||||
|
|
||||||
|
[RtIPC](https://gitlab.com/etherlab.org/rtipc) is needed.
|
||||||
|
Simply pass `--enable-fakeuserlib` to your `./configure` call
|
||||||
|
and the library will be built for you.
|
||||||
|
|
||||||
|
## How to set up dry run mode
|
||||||
|
|
||||||
|
### Redirect library loading
|
||||||
|
|
||||||
|
To avoid recompiling your application,
|
||||||
|
we will use `LD_LIBRARY_PATH` to load `libfakeethercat` instead of `libethercat`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# pick a location for an empty directory
|
||||||
|
export MY_LIB_LOCATION=$HOME/fake_lib64
|
||||||
|
# create that directory
|
||||||
|
mkdir -p $MY_LIB_LOCATION
|
||||||
|
# create a symlink to libfakeethercat
|
||||||
|
# assuming SOVERSION is 1
|
||||||
|
# debian users, please use /usr/lib/x86_64-linux-gnu/libfakeethercat.so.1
|
||||||
|
ln -s /usr/lib64/libfakeethercat.so.1 $MY_LIB_LOCATION/libethercat.so.1
|
||||||
|
# use MY_LIB_LOCATION to load libraries first
|
||||||
|
export LD_LIBRARY_PATH=$MY_LIB_LOCATION
|
||||||
|
# check whether everything is done right
|
||||||
|
ldd my_application | grep ethercat
|
||||||
|
libethercat.so.1 => /home/vh/fake_lib64/libethercat.so.1 (0x00007fa5a5c59000)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set up FakeEtherCAT Home
|
||||||
|
|
||||||
|
RtIPC needs a place to store its configuration.
|
||||||
|
Set `FAKE_EC_HOMEDIR` environment variabe to a path to an empty directory,
|
||||||
|
for instance `/tmp/FakeEtherCAT`.
|
||||||
|
`FAKE_EC_NAME` can be set to a useful name of your application,
|
||||||
|
default is `FakeEtherCAT`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export FAKE_EC_HOMEDIR=/tmp/FakeEtherCAT
|
||||||
|
rm -rf $FAKE_EC_HOMEDIR
|
||||||
|
mkdir -p $FAKE_EC_HOMEDIR
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spin up your application
|
||||||
|
|
||||||
|
Now it's time to simply launch your application.
|
||||||
|
You will notice that the PDO configuration will be dumped at stderr.
|
||||||
|
The path displayed is the path of the RtIPC variable in the following format:
|
||||||
|
`$FAKE_EC_PREFIX/$MASTER_ID/$DOMAIN_ID/$ALIAS$POSITION/$PDO`.
|
||||||
|
|
||||||
|
## How to emulate EtherCAT slaves
|
||||||
|
|
||||||
|
Let's say you'd like to build virtual EtherCAT slaves to emulate your field.
|
||||||
|
Libfakeethercat makes that possible with the help of RtIPC.
|
||||||
|
|
||||||
|
### Building a simulator
|
||||||
|
|
||||||
|
Your field emulator simply has to swap the sync manager direction setting
|
||||||
|
(EC_DIR_INPUT and EC_DIR_OUTPUT) in ec_sync_info_t.
|
||||||
|
Libfakeethercat instances use shared memory, provided by RtIPC,
|
||||||
|
to exchange process data.
|
||||||
|
The direction setting decides which instance writes and which reads from
|
||||||
|
shared memory.
|
||||||
|
|
||||||
|
For instance, emulating a digial output works in the following way:
|
||||||
|
Create another real time application with the same slave information
|
||||||
|
as your control application.
|
||||||
|
Then, replace all EC_DIR_INPUT with EC_DIR_OUTPUT and vice versa.
|
||||||
|
Now, remember to read process data instead of writing it, and vice versa.
|
||||||
|
So, your EL2004 digital out will be read by your simulating application
|
||||||
|
and has PDO object 0x1600 ff. configured with Sync Manager 2 as EC_DIR_INPUT.
|
||||||
|
|
||||||
|
[EtherLab](https://gitlab.com/etherlab.org/etherlab) provides
|
||||||
|
a convenient way to build an entire simulator using SIMULINK since
|
||||||
|
Version 2.4.0.
|
||||||
|
Run `web(etherlab_help_path('swap_io.html'), '-helpbrowser')`
|
||||||
|
in your MATLAB shell to read more about how to set up a
|
||||||
|
Process Data simulator.
|
||||||
|
|
||||||
|
### Start your application
|
||||||
|
|
||||||
|
First, do all the steps explained above to run your
|
||||||
|
control application in dry run mode.
|
||||||
|
Then, in another shell, do the same thing with your simulator,
|
||||||
|
but do not remove the `FAKE_EC_HOMEDIR` directory and
|
||||||
|
pick another `FAKE_EC_NAME`.
|
||||||
|
|
||||||
|
Carefully watch the PDO configuration on stderr and compare them.
|
||||||
|
All paths configured as Output on the control application have to
|
||||||
|
be configured as Input on your simulator and vice versa.
|
||||||
|
If you use multiple domains and there is a mismatch of the domain IDs,
|
||||||
|
set `FAKE_EC_DOMAIN_PERMUTATION` to a space-separated list of integers to
|
||||||
|
permutate the domain IDs of one application.
|
||||||
|
`FAKE_EC_DOMAIN_PERMUTATION="0 1"` swaps domains 0 and 1, for instance.
|
||||||
|
|
||||||
|
Finally, your control application needs to be restarted
|
||||||
|
so it can find the RtIPC variables which contains the process data of the
|
||||||
|
simulator.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
- FAKE_EC_DOMAIN_PERMUTATION: Permutate the domain IDs, useful to match control and simulation applications.
|
||||||
|
- FAKE_EC_HOMEDIR: Directory for RtIPC builletin board and SDO json files
|
||||||
|
- FAKE_EC_NAME: Will be used for naming RtIPC config and SDO json file
|
||||||
|
- FAKE_EC_PREFIX: Prefix for RtIPC variables, useful to run multiple simulators side by side.
|
||||||
|
|
@ -0,0 +1,693 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Bjarne von Horn, Ingenieurgemeinschaft IgH
|
||||||
|
*
|
||||||
|
* This file is part of the IgH EtherCAT master userspace library.
|
||||||
|
*
|
||||||
|
* The IgH EtherCAT master userspace library is free software; you can
|
||||||
|
* redistribute it and/or modify it under the terms of the GNU Lesser General
|
||||||
|
* Public License as published by the Free Software Foundation; version 2.1
|
||||||
|
* of the License.
|
||||||
|
*
|
||||||
|
* The IgH EtherCAT master userspace library 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with the IgH EtherCAT master userspace library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "fakeethercat.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <iterator>
|
||||||
|
#include <ios>
|
||||||
|
|
||||||
|
static std::vector<size_t> getPermutationVector(size_t count);
|
||||||
|
|
||||||
|
static std::ostream &operator<<(std::ostream &os, const sdo_address &a)
|
||||||
|
{
|
||||||
|
os << std::setfill('0') << std::hex << std::setw(6) << a.getCombined();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
static std::ostream &operator<<(std::ostream &os, const ec_address &a)
|
||||||
|
{
|
||||||
|
os << std::setfill('0') << std::hex << std::setw(8) << a.getCombined();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_spaces(std::ostream &out, int const num)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < num; ++i)
|
||||||
|
{
|
||||||
|
out << ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Map, typename Func>
|
||||||
|
static void map2Json(std::ostream &out, const Map &map, Func &&print_func, int indent = 0)
|
||||||
|
{
|
||||||
|
indent += 4;
|
||||||
|
out << "{";
|
||||||
|
bool is_first = true;
|
||||||
|
for (const auto &kv : map)
|
||||||
|
{
|
||||||
|
if (is_first)
|
||||||
|
{
|
||||||
|
out << '\n';
|
||||||
|
is_first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out << ",\n";
|
||||||
|
}
|
||||||
|
add_spaces(out, indent);
|
||||||
|
out << "\"0x" << std::hex << std::setfill('0') << std::setw(2 * sizeof(typename Map::key_type));
|
||||||
|
out << kv.first << "\": ";
|
||||||
|
print_func(out, kv.second);
|
||||||
|
}
|
||||||
|
out << '\n';
|
||||||
|
add_spaces(out, indent - 4);
|
||||||
|
out << "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pdo::sizeInBytes() const
|
||||||
|
{
|
||||||
|
size_t ans = 0;
|
||||||
|
for (const auto &entry : entries)
|
||||||
|
{
|
||||||
|
ans += entry.bit_length;
|
||||||
|
}
|
||||||
|
return (ans + 7) / 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
Offset pdo::findEntry(uint16_t idx, uint8_t subindex) const
|
||||||
|
{
|
||||||
|
size_t offset_bits = 0;
|
||||||
|
for (const auto &entry : entries)
|
||||||
|
{
|
||||||
|
if (entry.index == idx && entry.subindex == subindex)
|
||||||
|
{
|
||||||
|
return Offset(offset_bits / 8, offset_bits % 8);
|
||||||
|
}
|
||||||
|
offset_bits += entry.bit_length;
|
||||||
|
}
|
||||||
|
return NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec_domain::ec_domain(rtipc *rtipc, const char *prefix, ec_master_t *master) : rt_group(rtipc_create_group(rtipc, 1.0)), prefix(prefix), master(master)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int ec_domain::activate(int domain_id)
|
||||||
|
{
|
||||||
|
std::unordered_set<uint32_t> slaves;
|
||||||
|
|
||||||
|
connected.resize(mapped_pdos.size());
|
||||||
|
size_t idx = 0;
|
||||||
|
for (const auto &pdo : mapped_pdos)
|
||||||
|
{
|
||||||
|
slaves.insert(pdo.slave_address.getCombined());
|
||||||
|
void *rt_pdo = nullptr;
|
||||||
|
char buf[512];
|
||||||
|
const auto fmt = snprintf(buf, sizeof(buf), "%s/%d/%d/%08X/%04X", prefix, master->getId(), domain_id, pdo.slave_address.getCombined(), pdo.pdo_index);
|
||||||
|
if (fmt < 0 || fmt >= (int)sizeof(buf))
|
||||||
|
{
|
||||||
|
return -ENOBUFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (pdo.dir)
|
||||||
|
{
|
||||||
|
case EC_DIR_OUTPUT:
|
||||||
|
rt_pdo = rtipc_txpdo(rt_group, buf, rtipc_uint8_T, data.data() + pdo.offset, pdo.size_bytes);
|
||||||
|
std::cerr << "Registering " << buf << " as Output\n";
|
||||||
|
break;
|
||||||
|
case EC_DIR_INPUT:
|
||||||
|
rt_pdo = rtipc_rxpdo(rt_group, buf, rtipc_uint8_T, data.data() + pdo.offset, pdo.size_bytes, connected.data() + idx);
|
||||||
|
std::cerr << "Registering " << buf << " as Input\n";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::cerr << "Unknown direction " << pdo.dir << '\n';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!rt_pdo)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to register RtIPC PDO\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
activated_ = true;
|
||||||
|
numSlaves = slaves.size();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ec_domain::process()
|
||||||
|
{
|
||||||
|
rtipc_rx(rt_group);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ec_domain::queue()
|
||||||
|
{
|
||||||
|
rtipc_tx(rt_group);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t ec_domain::map(ec_slave_config const &config, unsigned int syncManager,
|
||||||
|
uint16_t pdo_index)
|
||||||
|
{
|
||||||
|
if (activated_)
|
||||||
|
return -1;
|
||||||
|
for (const auto &pdo : mapped_pdos)
|
||||||
|
{
|
||||||
|
if (pdo.slave_address == config.address && syncManager == pdo.syncManager && pdo_index == pdo.pdo_index)
|
||||||
|
{
|
||||||
|
// already mapped;
|
||||||
|
return pdo.offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto ans = data.size();
|
||||||
|
const auto size = config.sync_managers.at(syncManager).pdos.at(pdo_index).sizeInBytes();
|
||||||
|
mapped_pdos.emplace_back(ans, size, config.address, syncManager, pdo_index, config.sync_managers.at(syncManager).dir);
|
||||||
|
data.resize(ans + size);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *ecrt_domain_data(
|
||||||
|
const ec_domain_t *domain)
|
||||||
|
{
|
||||||
|
return domain->getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_domain_process(
|
||||||
|
ec_domain_t *domain)
|
||||||
|
{
|
||||||
|
return domain->process();
|
||||||
|
}
|
||||||
|
int ecrt_domain_queue(
|
||||||
|
ec_domain_t *domain)
|
||||||
|
{
|
||||||
|
return domain->queue();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_domain_state(
|
||||||
|
const ec_domain_t *domain, /**< Domain. */
|
||||||
|
ec_domain_state_t *state /**< Pointer to a state object to store the
|
||||||
|
information. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
state->working_counter = domain->getNumSlaves();
|
||||||
|
state->redundancy_active = 0;
|
||||||
|
state->wc_state = EC_WC_COMPLETE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ec_master::activate()
|
||||||
|
{
|
||||||
|
const auto permutate = getPermutationVector(domains.size());
|
||||||
|
int i = 0;
|
||||||
|
for (auto &domain : domains)
|
||||||
|
{
|
||||||
|
if (domain.activate(permutate[i]))
|
||||||
|
return -1;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::ofstream out(rt_ipc_dir + "/" + rt_ipc_name + "_slaves.json");
|
||||||
|
if (!out.is_open())
|
||||||
|
{
|
||||||
|
std::cerr << "could not dump json.\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
out << "{\n \"slaves\": ";
|
||||||
|
map2Json(out, slaves, [](std::ostream &out, const ec_slave_config &slave)
|
||||||
|
{ slave.dumpJson(out, 8); }, 4);
|
||||||
|
out << "\n}\n";
|
||||||
|
}
|
||||||
|
return rtipc_prepare(rt_ipc.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_master_activate(
|
||||||
|
ec_master_t *master /**< EtherCAT master. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return master->activate();
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
std::cerr << "Could not activate: " << e.what() << '\n';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_master_application_time(
|
||||||
|
ec_master_t *master, /**< EtherCAT master. */
|
||||||
|
uint64_t app_time /**< Application time. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec_domain_t *ecrt_master_create_domain(
|
||||||
|
ec_master_t *master /**< EtherCAT master. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return master->createDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *getPrefix()
|
||||||
|
{
|
||||||
|
if (const auto ans = getenv("FAKE_EC_PREFIX"))
|
||||||
|
return ans;
|
||||||
|
return "/FakeEtherCAT";
|
||||||
|
}
|
||||||
|
|
||||||
|
ec_domain *ec_master::createDomain()
|
||||||
|
{
|
||||||
|
domains.emplace_back(rt_ipc.get(), getPrefix(), this);
|
||||||
|
return &domains.back();
|
||||||
|
}
|
||||||
|
int ecrt_master_link_state(
|
||||||
|
const ec_master_t *master, /**< EtherCAT master. */
|
||||||
|
unsigned int dev_idx, /**< Index of the device (0 = main device, 1 =
|
||||||
|
first backup device, ...). */
|
||||||
|
ec_master_link_state_t *state /**< Structure to store the information.
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
{
|
||||||
|
state->slaves_responding = master->getNoSlaves();
|
||||||
|
state->al_states = 4;
|
||||||
|
state->link_up = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_master_receive(
|
||||||
|
ec_master_t *master /**< EtherCAT master. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_master_reset(
|
||||||
|
ec_master_t *master /**< EtherCAT master. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_master_scan_progress(
|
||||||
|
ec_master_t *master, /**< EtherCAT master */
|
||||||
|
ec_master_scan_progress_t *progress /**< Structure that will output
|
||||||
|
the progress information. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
progress->scan_index = progress->slave_count = master->getNoSlaves();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_master_send(
|
||||||
|
ec_master_t *master /**< EtherCAT master. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec_slave_config_t *ecrt_master_slave_config(
|
||||||
|
ec_master_t *master, /**< EtherCAT master */
|
||||||
|
uint16_t alias, /**< Slave alias. */
|
||||||
|
uint16_t position, /**< Slave position. */
|
||||||
|
uint32_t vendor_id, /**< Expected vendor ID. */
|
||||||
|
uint32_t product_code /**< Expected product code. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return master->slave_config(alias, position, vendor_id, product_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
ec_slave_config_t *ec_master::slave_config(
|
||||||
|
uint16_t alias, /**< Slave alias. */
|
||||||
|
uint16_t position, /**< Slave position. */
|
||||||
|
uint32_t vendor_id, /**< Expected vendor ID. */
|
||||||
|
uint32_t product_code /**< Expected product code. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const ec_address address{alias, position};
|
||||||
|
const auto it = slaves.find(address);
|
||||||
|
if (it != slaves.end())
|
||||||
|
{
|
||||||
|
if (it->second.vendor_id == vendor_id && it->second.product_code == product_code)
|
||||||
|
return &it->second;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Attempted to reconfigure slave (" << alias << "," << position << ")!\n";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return &slaves.insert(std::make_pair<ec_address, ec_slave_config>(ec_address{address}, ec_slave_config{address, vendor_id, product_code})).first->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int ecrt_master_state(
|
||||||
|
const ec_master_t *master, /**< EtherCAT master. */
|
||||||
|
ec_master_state_t *state /**< Structure to store the information. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
state->slaves_responding = master->getNoSlaves();
|
||||||
|
state->link_up = 1;
|
||||||
|
state->al_states = 8;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_master_sync_monitor_queue(
|
||||||
|
ec_master_t *master /**< EtherCAT master. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ecrt_master_sync_monitor_process(
|
||||||
|
const ec_master_t *master /**< EtherCAT master. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
int ecrt_master_sync_reference_clock(
|
||||||
|
ec_master_t *master /**< EtherCAT master. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_master_sync_reference_clock_to(
|
||||||
|
ec_master_t *master, /**< EtherCAT master. */
|
||||||
|
uint64_t sync_time /**< Sync reference clock to this time. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_master_sync_slave_clocks(
|
||||||
|
ec_master_t *master /**< EtherCAT master. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void ecrt_release_master(ec_master_t *master)
|
||||||
|
{
|
||||||
|
delete master;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec_master_t *ecrt_request_master(
|
||||||
|
unsigned int master_index /**< Index of the master to request. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return new ec_master(master_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *getName()
|
||||||
|
{
|
||||||
|
if (const auto ans = getenv("FAKE_EC_NAME"))
|
||||||
|
{
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
return "FakeEtherCAT";
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *getRtIpcDir()
|
||||||
|
{
|
||||||
|
if (const auto ans = getenv("FAKE_EC_HOMEDIR"))
|
||||||
|
{
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
return "/tmp/FakeEtherCAT";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<size_t> getPermutationVector(size_t count)
|
||||||
|
{
|
||||||
|
std::vector<size_t> ans;
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
ans.push_back(i);
|
||||||
|
}
|
||||||
|
const auto spec = getenv("FAKE_EC_DOMAIN_PERMUTATION");
|
||||||
|
if (!spec)
|
||||||
|
return ans;
|
||||||
|
std::istringstream is(spec);
|
||||||
|
std::istream_iterator<int> begin(is), end;
|
||||||
|
std::vector<int> values(begin, end);
|
||||||
|
if (values.size() % 2)
|
||||||
|
{
|
||||||
|
throw std::invalid_argument("Specify an even number of indices to permutate.\n");
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < values.size() / 2; ++i)
|
||||||
|
{
|
||||||
|
std::swap(ans.at(values[2 * i]), ans.at(values[2 * i + 1]));
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec_master::ec_master(int id) : rt_ipc_dir(getRtIpcDir()), rt_ipc_name(getName()), rt_ipc(rtipc_create(rt_ipc_name.c_str(), rt_ipc_dir.c_str())), id_(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_slave_config_complete_sdo(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||||
|
uint16_t index, /**< Index of the SDO to configure. */
|
||||||
|
const uint8_t *data, /**< Pointer to the data. */
|
||||||
|
size_t size /**< Size of the \a data. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return ecrt_slave_config_sdo(sc, index, 0, data, size);
|
||||||
|
}
|
||||||
|
ec_sdo_request_t *ecrt_slave_config_create_sdo_request(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||||
|
uint16_t index, /**< SDO index. */
|
||||||
|
uint8_t subindex, /**< SDO subindex. */
|
||||||
|
size_t size /**< Data size to reserve. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
int ecrt_slave_config_dc(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||||
|
uint16_t assign_activate, /**< AssignActivate word. */
|
||||||
|
uint32_t sync0_cycle, /**< SYNC0 cycle time [ns]. */
|
||||||
|
int32_t sync0_shift, /**< SYNC0 shift time [ns]. */
|
||||||
|
uint32_t sync1_cycle, /**< SYNC1 cycle time [ns]. */
|
||||||
|
int32_t sync1_shift /**< SYNC1 shift time [ns]. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int ecrt_slave_config_idn(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||||
|
uint8_t drive_no, /**< Drive number. */
|
||||||
|
uint16_t idn, /**< SoE IDN. */
|
||||||
|
ec_al_state_t state, /**< AL state in which to write the IDN (PREOP or
|
||||||
|
SAFEOP). */
|
||||||
|
const uint8_t *data, /**< Pointer to the data. */
|
||||||
|
size_t size /**< Size of the \a data. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int ecrt_slave_config_pdos(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||||
|
unsigned int n_syncs, /**< Number of sync manager configurations in
|
||||||
|
\a syncs. */
|
||||||
|
const ec_sync_info_t syncs[] /**< Array of sync manager
|
||||||
|
configurations. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!syncs)
|
||||||
|
return 0;
|
||||||
|
for (unsigned int sync_idx = 0; sync_idx < n_syncs; ++sync_idx)
|
||||||
|
{
|
||||||
|
if (syncs[sync_idx].index == 0xff)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto &manager = sc->sync_managers[syncs[sync_idx].index];
|
||||||
|
manager.dir = syncs[sync_idx].dir;
|
||||||
|
for (unsigned int i = 0; i < syncs[sync_idx].n_pdos; ++i)
|
||||||
|
{
|
||||||
|
const auto &in_pdo = syncs[sync_idx].pdos[i];
|
||||||
|
if (in_pdo.n_entries == 0 || !in_pdo.entries)
|
||||||
|
{
|
||||||
|
std::cerr << "Default mapping not supported.";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
auto &out_pdo = manager.pdos[in_pdo.index];
|
||||||
|
for (unsigned int pdo_entry_idx = 0; pdo_entry_idx < in_pdo.n_entries; ++pdo_entry_idx)
|
||||||
|
{
|
||||||
|
out_pdo.entries.push_back(in_pdo.entries[pdo_entry_idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int ecrt_domain_reg_pdo_entry_list(
|
||||||
|
ec_domain_t *domain, /**< Domain. */
|
||||||
|
const ec_pdo_entry_reg_t *pdo_entry_regs /**< Array of PDO
|
||||||
|
registrations. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const ec_pdo_entry_reg_t *reg;
|
||||||
|
ec_slave_config_t *sc;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
for (reg = pdo_entry_regs; reg->index; reg++)
|
||||||
|
{
|
||||||
|
if (!(sc = ecrt_master_slave_config(domain->getMaster(), reg->alias,
|
||||||
|
reg->position, reg->vendor_id, reg->product_code)))
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
if ((ret = ecrt_slave_config_reg_pdo_entry(sc, reg->index,
|
||||||
|
reg->subindex, domain, reg->bit_position)) < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
*reg->offset = ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_slave_config_reg_pdo_entry(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||||
|
uint16_t entry_index, /**< Index of the PDO entry to register. */
|
||||||
|
uint8_t entry_subindex, /**< Subindex of the PDO entry to register. */
|
||||||
|
ec_domain_t *domain, /**< Domain. */
|
||||||
|
unsigned int *bit_position /**< Optional address if bit addressing
|
||||||
|
is desired */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
for (auto sync_it : sc->sync_managers)
|
||||||
|
{
|
||||||
|
for (auto pdo_it : sync_it.second.pdos)
|
||||||
|
{
|
||||||
|
const auto offset = pdo_it.second.findEntry(entry_index, entry_subindex);
|
||||||
|
if (offset != NotFound)
|
||||||
|
{
|
||||||
|
const auto domain_offset = domain->map(*sc, sync_it.first, pdo_it.first);
|
||||||
|
if (domain_offset != -1)
|
||||||
|
{
|
||||||
|
if (bit_position)
|
||||||
|
*bit_position = offset.bits;
|
||||||
|
else if (!offset.bits)
|
||||||
|
{
|
||||||
|
std::cerr << "Pdo Entry is not byte aligned but bit offset is ignored!\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return domain_offset + offset.bytes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1; // offset
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_slave_config_sdo8(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration */
|
||||||
|
uint16_t sdo_index, /**< Index of the SDO to configure. */
|
||||||
|
uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */
|
||||||
|
uint8_t value /**< Value to set. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return ecrt_slave_config_sdo(sc, sdo_index, sdo_subindex, &value, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_slave_config_sdo16(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration */
|
||||||
|
uint16_t sdo_index, /**< Index of the SDO to configure. */
|
||||||
|
uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */
|
||||||
|
uint16_t const value /**< Value to set. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
uint8_t buf[sizeof(value)];
|
||||||
|
memcpy(&buf, &value, sizeof(value));
|
||||||
|
return ecrt_slave_config_sdo(sc, sdo_index, sdo_subindex, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_slave_config_sdo32(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration */
|
||||||
|
uint16_t sdo_index, /**< Index of the SDO to configure. */
|
||||||
|
uint8_t sdo_subindex, /**< Subindex of the SDO to configure. */
|
||||||
|
uint32_t const value /**< Value to set. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
uint8_t buf[sizeof(value)];
|
||||||
|
memcpy(&buf, &value, sizeof(value));
|
||||||
|
return ecrt_slave_config_sdo(sc, sdo_index, sdo_subindex, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ecrt_slave_config_sdo(
|
||||||
|
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||||
|
uint16_t index, /**< Index of the SDO to configure. */
|
||||||
|
uint8_t subindex, /**< Subindex of the SDO to configure. */
|
||||||
|
const uint8_t *data, /**< Pointer to the data. */
|
||||||
|
size_t size /**< Size of the \a data. */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
sc->sdos[sdo_address{index, subindex}] = std::basic_string<uint8_t>(data, data + size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ecrt_write_lreal(void *data, double const value)
|
||||||
|
{
|
||||||
|
memcpy(data, &value, sizeof(value));
|
||||||
|
}
|
||||||
|
void ecrt_write_real(void *data, float const value)
|
||||||
|
{
|
||||||
|
memcpy(data, &value, sizeof(value));
|
||||||
|
}
|
||||||
|
float ecrt_read_real(const void *data)
|
||||||
|
{
|
||||||
|
float ans;
|
||||||
|
memcpy(&ans, data, sizeof(ans));
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ecrt_read_lreal(const void *data)
|
||||||
|
{
|
||||||
|
double ans;
|
||||||
|
memcpy(&ans, data, sizeof(ans));
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ec_slave_config::dumpJson(std::ostream &out, int indent) const
|
||||||
|
{
|
||||||
|
out << "{\n";
|
||||||
|
indent += 4;
|
||||||
|
add_spaces(out, indent);
|
||||||
|
out << "\"vendor_id\": " << std::dec << vendor_id << ",\n";
|
||||||
|
add_spaces(out, indent);
|
||||||
|
out << "\"product_id\": " << product_code << ",\n";
|
||||||
|
add_spaces(out, indent);
|
||||||
|
out << "\"sdos\": ";
|
||||||
|
map2Json(out, sdos, [](std::ostream &out, const std::basic_string<uint8_t> &value)
|
||||||
|
{
|
||||||
|
out << "\"0x";
|
||||||
|
for (const auto s : value) {
|
||||||
|
out << std::hex << std::setfill('0') << std::setw(2) << (unsigned)s;
|
||||||
|
}
|
||||||
|
out << '"'; }, indent);
|
||||||
|
out << '\n';
|
||||||
|
add_spaces(out, indent - 4);
|
||||||
|
out << "}";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Bjarne von Horn, Ingenieurgemeinschaft IgH
|
||||||
|
*
|
||||||
|
* This file is part of the IgH EtherCAT master userspace library.
|
||||||
|
*
|
||||||
|
* The IgH EtherCAT master userspace library is free software; you can
|
||||||
|
* redistribute it and/or modify it under the terms of the GNU Lesser General
|
||||||
|
* Public License as published by the Free Software Foundation; version 2.1
|
||||||
|
* of the License.
|
||||||
|
*
|
||||||
|
* The IgH EtherCAT master userspace library 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with the IgH EtherCAT master userspace library. If not, see
|
||||||
|
* <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ecrt.h>
|
||||||
|
#include <rtipc.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct Offset
|
||||||
|
{
|
||||||
|
int bytes;
|
||||||
|
int bits;
|
||||||
|
|
||||||
|
constexpr Offset(int bytes,
|
||||||
|
int bits) : bytes(bytes), bits(bits) {}
|
||||||
|
|
||||||
|
constexpr bool operator!=(const Offset &other) const noexcept
|
||||||
|
{
|
||||||
|
return bytes != other.bytes || bits != other.bits;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr Offset NotFound(-1, -1);
|
||||||
|
|
||||||
|
struct pdo
|
||||||
|
{
|
||||||
|
std::vector<ec_pdo_entry_info_t> entries;
|
||||||
|
|
||||||
|
size_t sizeInBytes() const;
|
||||||
|
|
||||||
|
Offset findEntry(uint16_t idx, uint8_t subindex) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct syncManager
|
||||||
|
{
|
||||||
|
ec_direction_t dir;
|
||||||
|
std::map<uint16_t /* address */, pdo> pdos;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ec_address
|
||||||
|
{
|
||||||
|
uint32_t value;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ec_address(uint16_t alias, /**< Slave alias. */
|
||||||
|
uint16_t position /**< Slave position. */)
|
||||||
|
: value(static_cast<uint32_t>(alias) << 16 | position)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getAlias() const { return value >> 16; }
|
||||||
|
uint16_t getPosition() const { return value & 0xFFFF; }
|
||||||
|
uint32_t getCombined() const { return value; }
|
||||||
|
|
||||||
|
bool operator<(const ec_address &other) const noexcept
|
||||||
|
{
|
||||||
|
return value < other.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const ec_address &other) const noexcept
|
||||||
|
{
|
||||||
|
return value == other.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class sdo_address
|
||||||
|
{
|
||||||
|
uint32_t value;
|
||||||
|
|
||||||
|
public:
|
||||||
|
sdo_address(uint16_t index, /**< Slave alias. */
|
||||||
|
uint8_t subindex /**< Slave position. */)
|
||||||
|
: value(static_cast<uint32_t>(index) << 8 | subindex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getIndex() const { return value >> 8; }
|
||||||
|
uint8_t getSubIndex() const { return value & 0xFF; }
|
||||||
|
uint32_t getCombined() const { return value; }
|
||||||
|
|
||||||
|
bool operator<(const sdo_address &other) const noexcept
|
||||||
|
{
|
||||||
|
return value < other.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const sdo_address &other) const noexcept
|
||||||
|
{
|
||||||
|
return value == other.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ec_slave_config
|
||||||
|
{
|
||||||
|
ec_address address;
|
||||||
|
uint32_t vendor_id; /**< Expected vendor ID. */
|
||||||
|
uint32_t product_code; /**< Expected product code. */
|
||||||
|
std::map<unsigned int, syncManager> sync_managers;
|
||||||
|
std::map<sdo_address, std::basic_string<uint8_t>> sdos;
|
||||||
|
|
||||||
|
ec_slave_config(
|
||||||
|
ec_address address,
|
||||||
|
uint32_t vendor_id, /**< Expected vendor ID. */
|
||||||
|
uint32_t product_code /**< Expected product code. */)
|
||||||
|
: address(address), vendor_id(vendor_id), product_code(product_code)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void dumpJson(std::ostream &out, int indent) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ec_domain
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PdoMap
|
||||||
|
{
|
||||||
|
size_t offset;
|
||||||
|
size_t size_bytes;
|
||||||
|
ec_address slave_address;
|
||||||
|
unsigned int syncManager;
|
||||||
|
uint16_t pdo_index;
|
||||||
|
ec_direction_t dir;
|
||||||
|
|
||||||
|
PdoMap(
|
||||||
|
size_t offset,
|
||||||
|
size_t size_bytes,
|
||||||
|
ec_address slave_address,
|
||||||
|
unsigned int syncManager,
|
||||||
|
uint16_t pdo_index,
|
||||||
|
ec_direction_t dir)
|
||||||
|
: offset(offset), size_bytes(size_bytes), slave_address(slave_address), syncManager(syncManager), pdo_index(pdo_index), dir(dir)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
std::vector<unsigned char> connected;
|
||||||
|
std::vector<PdoMap> mapped_pdos;
|
||||||
|
rtipc_group *rt_group;
|
||||||
|
const char *prefix;
|
||||||
|
ec_master_t *master;
|
||||||
|
bool activated_ = false;
|
||||||
|
size_t numSlaves = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ec_domain(struct rtipc *rtipc, const char *prefix, ec_master_t *master);
|
||||||
|
|
||||||
|
uint8_t *getData() const
|
||||||
|
{
|
||||||
|
if (!activated_)
|
||||||
|
return nullptr;
|
||||||
|
return const_cast<uint8_t *>(data.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
int activate(int domain_id);
|
||||||
|
int process();
|
||||||
|
int queue();
|
||||||
|
|
||||||
|
size_t getNumSlaves() const { return numSlaves; }
|
||||||
|
|
||||||
|
ssize_t map(ec_slave_config const &config, unsigned int syncManager,
|
||||||
|
uint16_t pdo_index);
|
||||||
|
|
||||||
|
ec_master_t *getMaster() const { return master; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ec_master
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct RtIpcDeleter
|
||||||
|
{
|
||||||
|
void operator()(struct rtipc *r) const
|
||||||
|
{
|
||||||
|
rtipc_exit(r);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string rt_ipc_dir;
|
||||||
|
std::string rt_ipc_name;
|
||||||
|
std::list<ec_domain> domains;
|
||||||
|
std::map<ec_address, ec_slave_config> slaves;
|
||||||
|
std::unique_ptr<struct rtipc, RtIpcDeleter> rt_ipc;
|
||||||
|
int id_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ec_master(int id);
|
||||||
|
|
||||||
|
int activate();
|
||||||
|
ec_domain *createDomain();
|
||||||
|
|
||||||
|
int getNoSlaves() const { return slaves.size(); }
|
||||||
|
int getId() const { return id_; }
|
||||||
|
|
||||||
|
ec_slave_config_t *slave_config(
|
||||||
|
uint16_t alias, /**< Slave alias. */
|
||||||
|
uint16_t position, /**< Slave position. */
|
||||||
|
uint32_t vendor_id, /**< Expected vendor ID. */
|
||||||
|
uint32_t product_code /**< Expected product code. */
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -82,6 +82,9 @@ LIBETHERCAT_1.5.2 {
|
||||||
ecrt_voe_handler_received_header;
|
ecrt_voe_handler_received_header;
|
||||||
ecrt_voe_handler_send_header;
|
ecrt_voe_handler_send_header;
|
||||||
ecrt_voe_handler_write;
|
ecrt_voe_handler_write;
|
||||||
|
local:
|
||||||
|
# Hide all C++ symbols in libfakeethercat
|
||||||
|
_Z*;
|
||||||
};
|
};
|
||||||
|
|
||||||
LIBETHERCAT_1.5.3 {
|
LIBETHERCAT_1.5.3 {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,9 @@
|
||||||
A list of all native network card drivers can be found
|
A list of all native network card drivers can be found
|
||||||
<a href="devicedrivers.html">here</a>.
|
<a href="devicedrivers.html">here</a>.
|
||||||
|
|
||||||
|
A <a href="libfakeethercat.html">second userspace library</a> can be used for a dry-run mode
|
||||||
|
or simulating Process Data.
|
||||||
|
|
||||||
For information how to build and install, see the INSTALL file in the source
|
For information how to build and install, see the INSTALL file in the source
|
||||||
root.
|
root.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue