Add README to fakelib
This commit is contained in:
parent
c15195178f
commit
c2c3d56a2c
|
|
@ -658,6 +658,7 @@ WARN_LOGFILE =
|
||||||
|
|
||||||
INPUT = @top_srcdir@/master \
|
INPUT = @top_srcdir@/master \
|
||||||
@top_srcdir@/include \
|
@top_srcdir@/include \
|
||||||
|
@top_srcdir@/fake_lib/README.md \
|
||||||
@top_srcdir@/devices/ecdev.h \
|
@top_srcdir@/devices/ecdev.h \
|
||||||
@top_builddir@/device_drivers.md
|
@top_builddir@/device_drivers.md
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,13 @@ http://etherlab.org/en/ethercat/hardware.php.
|
||||||
|
|
||||||
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 maser 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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -103,7 +103,7 @@ Offset pdo::findEntry(uint16_t idx, uint8_t subindex) const
|
||||||
return NotFound;
|
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");
|
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";
|
std::cerr << "could not dump json.\n";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
out << "{\n \"slaves\": ";
|
out << "{\n \"slaves\": ";
|
||||||
map2Json(out, slaves, [](std::ostream& out, const ec_slave_config& slave) {
|
map2Json(out, slaves, [](std::ostream &out, const ec_slave_config &slave)
|
||||||
slave.dumpJson(out, 8);
|
{ slave.dumpJson(out, 8); }, 4);
|
||||||
}, 4);
|
|
||||||
out << "\n}\n";
|
out << "\n}\n";
|
||||||
}
|
}
|
||||||
return rtipc_prepare(rt_ipc.get());
|
return rtipc_prepare(rt_ipc.get());
|
||||||
|
|
@ -266,9 +266,16 @@ ec_domain_t *ecrt_master_create_domain(
|
||||||
return master->createDomain();
|
return master->createDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *getPrefix()
|
||||||
|
{
|
||||||
|
if (const auto ans = getenv("FAKE_EC_PREFIX"))
|
||||||
|
return ans;
|
||||||
|
return "/FakeEtherCAT";
|
||||||
|
}
|
||||||
|
|
||||||
ec_domain *ec_master::createDomain()
|
ec_domain *ec_master::createDomain()
|
||||||
{
|
{
|
||||||
domains.emplace_back(rt_ipc.get(), "/FakeTaxi");
|
domains.emplace_back(rt_ipc.get(), getPrefix(), this);
|
||||||
return &domains.back();
|
return &domains.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,7 +393,16 @@ static const char *getName()
|
||||||
{
|
{
|
||||||
return ans;
|
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)
|
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);
|
ans.push_back(i);
|
||||||
}
|
}
|
||||||
const auto spec = getenv("FAKE_DOMAIN_PERMUTATION");
|
const auto spec = getenv("FAKE_EC_DOMAIN_PERMUTATION");
|
||||||
if (!spec)
|
if (!spec)
|
||||||
return ans;
|
return ans;
|
||||||
std::istringstream is(spec);
|
std::istringstream is(spec);
|
||||||
|
|
@ -413,7 +429,7 @@ static std::vector<size_t> getPermutationVector(size_t count)
|
||||||
return ans;
|
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;
|
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(
|
int ecrt_slave_config_reg_pdo_entry(
|
||||||
ec_slave_config_t *sc, /**< Slave configuration. */
|
ec_slave_config_t *sc, /**< Slave configuration. */
|
||||||
uint16_t entry_index, /**< Index of the PDO entry to register. */
|
uint16_t entry_index, /**< Index of the PDO entry to register. */
|
||||||
|
|
|
||||||
|
|
@ -166,11 +166,12 @@ private:
|
||||||
std::vector<PdoMap> mapped_pdos;
|
std::vector<PdoMap> mapped_pdos;
|
||||||
rtipc_group *rt_group;
|
rtipc_group *rt_group;
|
||||||
const char *prefix;
|
const char *prefix;
|
||||||
|
ec_master_t *master;
|
||||||
bool activated_ = false;
|
bool activated_ = false;
|
||||||
size_t numSlaves = 0;
|
size_t numSlaves = 0;
|
||||||
|
|
||||||
public:
|
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
|
uint8_t *getData() const
|
||||||
{
|
{
|
||||||
|
|
@ -187,6 +188,8 @@ public:
|
||||||
|
|
||||||
ssize_t map(ec_slave_config const &config, unsigned int syncManager,
|
ssize_t map(ec_slave_config const &config, unsigned int syncManager,
|
||||||
uint16_t pdo_index);
|
uint16_t pdo_index);
|
||||||
|
|
||||||
|
ec_master_t *getMaster() const { return master; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ec_master
|
struct ec_master
|
||||||
|
|
|
||||||
|
|
@ -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 EtherCAT slaves.
|
||||||
|
|
||||||
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