Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ else()
set(KICKCAT_INSTALL_DEFAULT OFF)
endif()

# Host-only components (tests, simulation, GUI tools, ESI parser, master examples) need desktop
# Host-only components (tests, simulation, GUI tools, ESI/ENI parsers, master examples) need desktop
# deps and default ON for Linux/Windows only; embedded targets get the library + their slave example.
if (UNIX OR WIN32)
set(KICKCAT_HOST ON)
Expand All @@ -28,6 +28,7 @@ option(BUILD_UNIT_TESTS "Build unit tests" OFF)
option(BUILD_SIMULATION "Build simulation" ${KICKCAT_HOST})
option(BUILD_TOOLS "Build tools" ${KICKCAT_HOST})
option(ENABLE_ESI_PARSER "Enable ESI XML parser" ${KICKCAT_HOST})
option(ENABLE_ENI_PARSER "Enable ENI XML parser" ${KICKCAT_HOST})
option(ENABLE_AF_XDP "Enable AF_XDP socket for improved performance (requires libxdp, libbpf, clang)" OFF)
option(ENABLE_CODE_COVERAGE "Enable code coverage (requires gcovr)" OFF)
option(ENABLE_ASAN "Build with AddressSanitizer" OFF)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Where to go next:
- Simulator and emulator -- [docs/SIMULATION.md](docs/SIMULATION.md)
- Tools and GUIs -- [docs/TOOLS.md](docs/TOOLS.md)
- Real-time performance tuning -- [docs/PERFORMANCE.md](docs/PERFORMANCE.md)
- ENI network configuration (experimental) -- [docs/ENI.md](docs/ENI.md)

## Platform support

Expand Down
3 changes: 2 additions & 1 deletion cmake/KickCATConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
include(CMakeFindDependencyMacro)

set(KICKCAT_ENABLE_ESI_PARSER @ENABLE_ESI_PARSER@)
set(KICKCAT_ENABLE_ENI_PARSER @ENABLE_ENI_PARSER@)
set(KICKCAT_ENABLE_AF_XDP @ENABLE_AF_XDP@)

if(KICKCAT_ENABLE_ESI_PARSER)
if(KICKCAT_ENABLE_ESI_PARSER OR KICKCAT_ENABLE_ENI_PARSER)
find_dependency(tinyxml2 CONFIG)
endif()

Expand Down
7 changes: 4 additions & 3 deletions conan/all/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class KickCATRecipe(ConanFile):
topics = ("ethercat")
package_type = "library"
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False], "fPIC": [True, False], "with_esi_parser": [True, False]}
default_options = {"shared": False, "fPIC": True, "with_esi_parser": False}
options = {"shared": [True, False], "fPIC": [True, False], "with_esi_parser": [True, False], "with_eni_parser": [True, False]}
default_options = {"shared": False, "fPIC": True, "with_esi_parser": False, "with_eni_parser": False}

def export_sources(self):
# This method is used for local development to copy sources into the recipe
Expand Down Expand Up @@ -61,13 +61,14 @@ def validate(self):

def requirements(self):
self.requires("argparse/3.2")
if self.options.with_esi_parser:
if self.options.with_esi_parser or self.options.with_eni_parser:
self.requires("tinyxml2/11.0.0")


def generate(self):
tc = CMakeToolchain(self)
tc.cache_variables["ENABLE_ESI_PARSER"] = bool(self.options.with_esi_parser)
tc.cache_variables["ENABLE_ENI_PARSER"] = bool(self.options.with_eni_parser)
tc.cache_variables["BUILD_UNIT_TESTS"] = "OFF"
tc.cache_variables["BUILD_MASTER_EXAMPLES"] = "OFF"
tc.cache_variables["BUILD_SLAVE_EXAMPLES"] = "OFF"
Expand Down
4 changes: 3 additions & 1 deletion conan/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class KickCATDev(ConanFile):
options = {
"unit_tests": [True, False],
"esi_parser": [True, False],
"eni_parser": [True, False],
"simulation": [True, False],
"tools": [True, False],
"master_examples": [True, False],
Expand All @@ -21,6 +22,7 @@ class KickCATDev(ConanFile):
default_options = {
"unit_tests": False,
"esi_parser": True,
"eni_parser": True,
"simulation": True,
"tools": True,
"master_examples": True,
Expand All @@ -34,7 +36,7 @@ def requirements(self):
if self.options.unit_tests:
self.requires("gtest/1.15.0")

if self.options.esi_parser:
if self.options.esi_parser or self.options.eni_parser:
self.requires("tinyxml2/11.0.0")

if self.options.simulation:
Expand Down
192 changes: 50 additions & 142 deletions docs/BUILDING.md
Original file line number Diff line number Diff line change
@@ -1,177 +1,85 @@
# Building KickCAT

KickCAT uses Conan (for dependencies) and CMake, wrapped by the scripts in
`scripts/`. Prefer the wrappers over invoking `cmake` / `conan` directly.

## Prerequisites

- **Linux**: gcc, cmake, conan
- **Python bindings**: uv or pip
- **Hardware access**: a network interface with raw-socket capability

## Linux (recommended)
The Linux development build uses the wrappers in `scripts/` to configure
Conan dependencies and CMake. You need a C++ compiler, CMake, Make, Python and
Conan 2.10 or newer. Activate the repository's Python environment before
running a script. If `.venv` does not exist yet, create it and install Conan:

```bash
# 1. Configure build options (optional -- defaults are sensible)
./scripts/configure.sh build --with=unit_tests

# 2. Set up the build environment (installs Conan deps + runs CMake)
./scripts/setup_build.sh build

# 3. Build
cd build && make -j

# 4. Grant network capabilities (required for raw-socket access)
sudo setcap 'cap_net_raw,cap_net_admin=+ep' ./tools/your_binary
python3 -m venv .venv
source .venv/bin/activate
python -m pip install 'conan>=2.10'
```

`configure.sh` stores the selected options in `.buildconfig`, which
`setup_build.sh` consumes. Available options include `unit_tests`,
`code_coverage`, `esi_parser`, `simulation`, `tools`, `master_examples`,
`slave_examples`, `eeprom_editor`, and `kickui`.

### Rebuild after source changes

From an existing `build` directory, no need to re-run `configure.sh` /
`setup_build.sh` unless CMakeLists or Conan deps changed:
Then build:

```bash
cd build && make -j
```

### GUI tools (KickUI, EEPROM editor)

These are off by default and need ImGui + GLFW:

```bash
./scripts/configure.sh build --with=kickui --with=eeprom_editor
source .venv/bin/activate
./scripts/configure.sh build --with=unit_tests
./scripts/setup_build.sh build
cd build && make -j
```

See [TOOLS.md](TOOLS.md) for what each tool does.

## Python bindings

```bash
# Install with uv (recommended)
uv pip install .

# Or for development (faster rebuilds)
uv pip install --no-build-isolation -Cbuild-dir=/tmp/build -v .
```

The wheel build enables the EEPROM-editor GUI (`BUILD_EEPROM_EDITOR=ON` in
`pyproject.toml`), which needs `imgui` and `glfw`. The cibuildwheel images
provide them; on a bare environment that lacks them, either install them (via
Conan) or disable the GUI for the build:

```bash
uv pip install --config-setting=cmake.define.BUILD_EEPROM_EDITOR=OFF .
```

The published package is available on PyPI:

```bash
pip install kickcat
```

The Python interpreter needs raw-socket capabilities to drive a bus:

```bash
./py_bindings/enable_raw_access.sh
```
`configure.sh` writes options to `build/.buildconfig`; `setup_build.sh` reads
them. The configuration step is optional if the defaults suit you. Run
`./scripts/configure.sh build --show` to see the current options, or
`./scripts/configure.sh build --help` for the complete list. After changing
build options, CMake files or dependencies, rerun `setup_build.sh`.

Examples live in `py_bindings/examples/`. `foe.py` transfers a file over FoE
(`Bus.read_foe` / `Bus.write_foe`, `ErrorFoE` on failure); it runs against the
simulator too:
For source changes, rebuild in the existing build directory:

```bash
./build/simulation/network_simulator -i tap:server -s simulation/slave_configs/ecat402-drive-foe.json &
python py_bindings/examples/foe.py -i tap:client -c write -f firmware.bin
cd build && make -j
```

### Multi-wheel (CI)

This project uses cibuildwheel to generate wheels for all supported
configurations. To run it locally:
`setup_build.sh` accepts `--build-type Debug` and `--target linux-aarch64`.
The latter needs the `aarch64-linux-gnu` cross compilers.

```bash
uvx cibuildwheel
```
## Tests and coverage

## Manual build (without the wrapper scripts)
With `unit_tests` enabled, run the test binary from the build directory:

```bash
# 1. Create the build directory
mkdir -p build

# 2. Install dependencies with Conan
python3 -m venv kickcat_venv
source kickcat_venv/bin/activate
pip install conan

conan install conan/conanfile.py -of=build/ \
-pr:h conan/your_profile_host.txt \
-pr:b conan/your_profile_target.txt \
--build=missing -s build_type=Release

# Or create the Conan package directly
conan create conan/all --build=missing --version 2.5 -pr build/profile.txt

# 3. Configure and build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j
./kickcat_unit
./kickcat_unit --gtest_filter='<Pattern>*'
```

## Windows

Windows is **not** suitable for real-time use, but it is useful for tools and
testing.

Requirements:
For coverage, install `gcovr`, enable both `unit_tests` and `code_coverage`,
then rebuild and run `make coverage` from the build directory.

- Conan for Windows (tested with 2.9.1)
- gcc for Windows (tested with w64devkit 2.0.0)
- npcap (driver 1.80 + SDK 1.70)
## Optional GUIs

Follow the manual build instructions above, using the appropriate Windows paths
and the `conan/profile_windows_x86_64.txt` profile.
KickUI and the EEPROM editor are off by default. They require ImGui and GLFW.
Enable them with `--with=kickui` and/or `--with=eeprom_editor`, then rerun
`setup_build.sh` and build. See [TOOLS.md](TOOLS.md) for their commands.

## PikeOS

Tested on PikeOS 5.1 for the native personality (p4ext).

Provide a CMake cross-toolchain file that defines the `PIKEOS` variable. Example
process/thread configurations are in `lib/src/OS/PikeOS/p4ext_config.c`.

## Slave firmware

Building and flashing NuttX slave firmware (XMC4800, Arduino Due, Freedom K64F)
is covered in [HARDWARE.md](HARDWARE.md).

## Testing
## Python bindings

### Unit tests
Install the Python package from the repository root with `uv pip install .`
or `pip install .`. The wheel build enables the EEPROM editor GUI by default;
if its GUI dependencies are unavailable, use:

```bash
./scripts/configure.sh build --with=unit_tests
./scripts/setup_build.sh build
cd build && make -j
make test
uv pip install --config-setting=cmake.define.BUILD_EEPROM_EDITOR=OFF .
```

The test binary is `build/kickcat_unit`. Run a subset with
`./kickcat_unit --gtest_filter='<Pattern>*'`.
The package is also published as `kickcat` on PyPI. To use a physical bus on
Linux, grant raw-socket capabilities to the executable or run
`./py_bindings/enable_raw_access.sh` for the active Python interpreter. See
the [hardware guide](HARDWARE.md) for bus setup and
`py_bindings/examples/` for Python examples.

### Code coverage
## Other platforms

```bash
uv pip install gcovr
The Linux wrappers generate Linux toolchains. Windows builds use the MinGW
profile in `conan/profile_windows_x86_64.txt`; see the
[CI workflow](../.github/workflows/ci.yml) for the current build setup.
Windows is intended for tools and tests, not real-time use.

./scripts/configure.sh build --with=unit_tests --with=code_coverage
./scripts/setup_build.sh build
cd build && make -j
make coverage
```
PikeOS 5.1 native personality builds require a cross-toolchain file defining
`PIKEOS`. Example process and thread configurations are in
`lib/src/OS/PikeOS/p4ext_config.c`.

NuttX slave firmware build and flashing instructions are in
[HARDWARE.md](HARDWARE.md).
63 changes: 63 additions & 0 deletions docs/ENI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# ENI support

KickCAT can load an EtherCAT Network Information (ENI) XML file and execute its
startup commands to configure a master bus. This support is **experimental** and
has been tested only with the in-process simulator.

Enable `eni_parser` to load XML files. The `eni_sequencer` example also needs
`esi_parser` and `master_examples` (all enabled by default). See
[BUILDING.md](BUILDING.md) for prerequisites:

```bash
source .venv/bin/activate
./scripts/configure.sh build --with=eni_parser --with=esi_parser --with=master_examples
./scripts/setup_build.sh build
cd build && make -j
cd ..
./build/examples/master/eni_sequencer/eni_sequencer \
-n unit/kickcat_eni_test_drives.xml \
-e simulation/slave_configs/ecat402-drive.xml
```

`eni_sequencer` creates a simulated bus from the ESI file. To inspect an ENI
without starting a bus, run `./build/examples/master/load_eni/load_eni -f FILE`.

## Using the sequencer

`ENI::loadFile()` returns a configuration for `ENI::Sequencer`. Keep that
configuration and the link alive for as long as the sequencer uses them. The
sequencer runs the ENI startup commands; do not also call `Bus::init()` or
`Bus::createMapping()`.

```cpp
ENI::Sequencer sequencer(bus, link, config);
sequencer.requestState(State::SAFE_OP);

std::vector<uint8_t> iomap(sequencer.processImageSize());
sequencer.mapProcessImage(iomap.data(), iomap.size());
bus.processDataReadWrite(error);

sequencer.requestState(State::OPERATIONAL, [&]()
{
bus.processDataReadWrite(error);
});
```

The process image becomes available at SAFE_OP. Outputs must be exchanged
before requesting OP; the callback keeps them cycling while the transition is
checked. See [`eni_sequencer.cc`](../examples/master/eni_sequencer/eni_sequencer.cc)
for a complete example. `cycleTime()` and `shiftTime()` expose the common DC
timing, when the ENI specifies it, for `Bus::enableDC()`.

## Current limits

- The detected slave count and identities must match the ENI and the devices'
SII data. Mailbox and process data layouts are checked against the registers
programmed by the ENI.
- SoE, EoE, FoE, AoE and VoE startup commands are not executed. Bootstrap,
HotConnect and `PreviousPort` checks are not supported.
- ENI `<Cyclic>` frames, `<ProcessImage>` variables and `MailboxStates` are
parsed but not used by the sequencer. The regular `Bus` exchange uses the
process data mapping read back from the slaves.
- Each startup command uses its own round trip; `Requires=frame/cycle` is not
implemented. A failed command throws, without automatic recovery.
2 changes: 2 additions & 0 deletions examples/master/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ add_subdirectory(common)
add_subdirectory(ds402_drive)
add_subdirectory(easycat)
add_subdirectory(elmo)
add_subdirectory(eni_sequencer)
add_subdirectory(freedom-k64f)
add_subdirectory(ingenia)
add_subdirectory(load_eni)
add_subdirectory(load_esi)
add_subdirectory(simulated_bus)
add_subdirectory(wdc_foot)
Expand Down
5 changes: 5 additions & 0 deletions examples/master/eni_sequencer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
if (ENABLE_ENI_PARSER AND ENABLE_ESI_PARSER)
add_executable(eni_sequencer eni_sequencer.cc)
target_link_libraries(eni_sequencer kickcat argparse::argparse)
set_kickcat_properties(eni_sequencer)
endif ()
Loading
Loading