Add README to fakelib

This commit is contained in:
Bjarne von Horn 2024-07-18 12:02:53 +02:00
parent c15195178f
commit c2c3d56a2c
7 changed files with 198 additions and 10 deletions

View File

@ -658,6 +658,7 @@ WARN_LOGFILE =
INPUT = @top_srcdir@/master \
@top_srcdir@/include \
@top_srcdir@/fake_lib/README.md \
@top_srcdir@/devices/ecdev.h \
@top_builddir@/device_drivers.md

View File

@ -72,6 +72,13 @@ http://etherlab.org/en/ethercat/hardware.php.
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 maser or with emulated EtherCAT slaves.
Please find some details [here](fake_lib/README.md).
# Realtime and Tuning
Realtime patches for the Linux kernel are supported, but not required. The

129
fake_lib/README.md Normal file
View File

@ -0,0 +1,129 @@
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/$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) will be adapted soon
to provide a convenient way to build an entire simulator using SIMULINK.
### 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.

View File

@ -103,7 +103,7 @@ Offset pdo::findEntry(uint16_t idx, uint8_t subindex) const
return NotFound;
}
ec_domain::ec_domain(rtipc *rtipc, const char *prefix) : rt_group(rtipc_create_group(rtipc, 1.0)), prefix(prefix)
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)
{
}
@ -223,14 +223,14 @@ int ec_master::activate()
}
{
std::ofstream out(rt_ipc_dir + "/" + rt_ipc_name + "_slaves.json");
if (!out.is_open()) {
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);
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());
@ -266,9 +266,16 @@ ec_domain_t *ecrt_master_create_domain(
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(), "/FakeTaxi");
domains.emplace_back(rt_ipc.get(), getPrefix(), this);
return &domains.back();
}
@ -386,7 +393,16 @@ static const char *getName()
{
return ans;
}
return "FakeTaxi";
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)
@ -396,7 +412,7 @@ static std::vector<size_t> getPermutationVector(size_t count)
{
ans.push_back(i);
}
const auto spec = getenv("FAKE_DOMAIN_PERMUTATION");
const auto spec = getenv("FAKE_EC_DOMAIN_PERMUTATION");
if (!spec)
return ans;
std::istringstream is(spec);
@ -413,7 +429,7 @@ static std::vector<size_t> getPermutationVector(size_t count)
return ans;
}
ec_master::ec_master() : rt_ipc_dir("/tmp/FakeTaxi"), rt_ipc_name(getName()), rt_ipc(rtipc_create(rt_ipc_name.c_str(), rt_ipc_dir.c_str()))
ec_master::ec_master() : rt_ipc_dir(getRtIpcDir()), rt_ipc_name(getName()), rt_ipc(rtipc_create(rt_ipc_name.c_str(), rt_ipc_dir.c_str()))
{
}
@ -494,6 +510,32 @@ int ecrt_slave_config_pdos(
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. */

View File

@ -166,11 +166,12 @@ private:
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);
explicit ec_domain(struct rtipc *rtipc, const char *prefix, ec_master_t *master);
uint8_t *getData() const
{
@ -187,6 +188,8 @@ public:
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

View File

@ -82,6 +82,9 @@ LIBETHERCAT_1.5.2 {
ecrt_voe_handler_received_header;
ecrt_voe_handler_send_header;
ecrt_voe_handler_write;
local:
# Hide all C++ symbols in libfakeethercat
_Z*;
};
LIBETHERCAT_1.5.3 {

View File

@ -40,6 +40,9 @@
A list of all native network card drivers can be found
<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 EtherCAT slaves.
For information how to build and install, see the INSTALL file in the source
root.