diff --git a/Doxyfile.in b/Doxyfile.in index 304a670a..68edea77 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -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 diff --git a/README.md b/README.md index 11411cf3..3ec5876a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/fake_lib/README.md b/fake_lib/README.md new file mode 100644 index 00000000..de104f2e --- /dev/null +++ b/fake_lib/README.md @@ -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. diff --git a/fake_lib/fakeethercat.cpp b/fake_lib/fakeethercat.cpp index 8a9f77ec..331db186 100644 --- a/fake_lib/fakeethercat.cpp +++ b/fake_lib/fakeethercat.cpp @@ -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 getPermutationVector(size_t count) @@ -396,7 +412,7 @@ static std::vector 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 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. */ diff --git a/fake_lib/fakeethercat.h b/fake_lib/fakeethercat.h index 0b0b2d29..1a06dd59 100644 --- a/fake_lib/fakeethercat.h +++ b/fake_lib/fakeethercat.h @@ -166,11 +166,12 @@ private: std::vector 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 diff --git a/lib/libethercat.map b/lib/libethercat.map index 759e6b58..4f074c26 100644 --- a/lib/libethercat.map +++ b/lib/libethercat.map @@ -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 { diff --git a/master/doxygen.c b/master/doxygen.c index 156f9175..2446f707 100644 --- a/master/doxygen.c +++ b/master/doxygen.c @@ -40,6 +40,9 @@ A list of all native network card drivers can be found here. + A second userspace library 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.