The full TiptoP bicycling simulator documentation

by Aboozar Roosta
Chair of Bicycle Traffic, University of Wuppertal

1. Overview and Design Philosophy

This document serves as the technical documentation for TiptoP Bicycling Simulator, a bicycling simulator developed by the Chair of Bicycle Traffic at the University of Wuppertal. The simulator was specifically engineered to be able to simulate the hilly terrain and graded roads similar to those found in the Bergisches Land Region (Link to German Wikipedia page: https://de.wikipedia.org/wiki/Bergisches_Land) where the university is located, as well as any real or artificial scenarios where simulations of graded roads are desired. Our work builds upon an earlier simulator design at the Technical University of Munich (TUM) by Paul Pabst under Supervision of Patrick Malcolm. We have improved this earlier design by integrating ANT+ connectivity for the Wahoo Kickr Bike, adding gradient responsiveness and creating a custom workflow for generating 3D maps of real-world locations, such as our implementation of the city of Wuppertal (see the Maps Library).

We have created two 3D maps for our research in the TiptoP project which are published with open license. Map 1 is the map of the our main University Campus which is located in the hills and a round tour around it which covers steep uphills as well as steep downhills. Map 2 on the other hand is a complete custom map which was designed to study perceived effort of cyclists by increasing the steepness of the route in a step-wise manner. These two maps show some of the scenarios that can be simulated with our simulator and workflow. Our simulator can be run with most maps that can be loaded or created in the CARLA simulator. An exception to the current simulator are the maps that have roads going above each other (like bridges). That being said, the focus of our simulator is on behavior while going uphill, downhill or flat riding. Due to hardware and software limitations, short term bicycling behavior can not be realistically simulated.

We have strived to use open hardware and software components in the design and development of the TiptoP bicycling simulator to the extent that our time and expertise has allowed us. Some components of the simulator are not open, and the licensing is strict. Specifically, the TiptoP bicycling simulator runs on Windows 11, a non-open operating system, uses Garmin ANT+ technology with very strict development content licensing, uses Roadrunner, a non-open plugin for Mathworks Matlab, which is proprietary software software. The bicycling dynamics model from the bike trainer is a black-box and we could not find any information regarding it. We have also used an Arduino board as the base of our communications.

On the other hand, CARLA is an open simulator, we publish our code and 3D maps under open licenses (except codes for ANT+ protocol which we are limited by the strict Garmin licensing) and we use open hardware-software platforms like Arduino. We also share hardware design material for TiptoP bicycling simulator as well as collected data during our experiments. We encourage all who use our designs to publish their contributions under open licenses.

At the chair of bicycle traffic, we believe that open source design and publication is an engine for growth and we strive to follow these principles wherever we are not restricted by external constraints such as licensing or time and resource limitations.

1.1 System Architecture

Tiptop bicycling simulator operates as a human-in-the-loop system comprising of three main hardware components: the physical bicycle platform (Wahoo Kickr Bike), a communication board based on Arduino, and the simulator PC. The Simulator PC runs the 3D environment, logs sensor and bike trainer data, and dynamically controls the bike’s incline based on the simulated terrain in the 3D simulator.

Figure 1 outlines the data and command flow between these components. The diagram illustrates different subsystems of the TiptoP bicycling simulator, the values and commands transferred between these subsystems as well as the medium and type of connections.

Figure 1: Data and Command Flow
Figure 1: Data and Command Flow

The diagram in Figure 1 also provides an overview of how information moves through the simulator. Sensor data such as power, speed and cadence from the bike trainer is transmitted to the Simulator PC via ANT+, while the communication board supplies steering and wheel-speed measurements over Ethernet at 50 Hz. The Simulator PC integrates these data streams, updates the 3D environment, and issues control commands over ANT+ protocol back to the bike trainer to adjust incline and rider weight parameters. Display output is continuously sent to the screens through standard video interfaces such as DVI and HDMI (depending on the graphics card output ports and configuration). Together, these connections illustrate how rider input, simulation logic, and physical feedback form a continuous loop within the system.

1.2 Hardware

The Tiptop bicycling simulator uses Wahoo Kickr Bike, an adjustable standalone bike trainer with resistance and incline simulation and ANT+ connectivity features as the base bicycle. To capture ride data, we use a hybrid sensor approach. While we read standard metrics like power, cadence and speed directly from the bike trainer’s internal ANT+ broadcast, we installed custom external sensors for more frequent updates required for a minimum lag bicycling experience.

FIgure 2: Wahoo Kickr Bike serves as the bicycle platform in TiptoP bicycling simulator (to be replaced with better picture if necessary)

For steering, we utilize an absolute encoder as a sensor. This is essential for measuring the handlebar’s absolute deviation from the perpendicular starting position. For speed, absolute positioning is unnecessary, so we use an incremental encoder mounted on the bike trainer’s artificial rear wheel. This sensor generates 1024 pulses per full rotation, providing high-resolution speed data. We call this back wheel artificial, as it does not have the dimensions of normal bike wheels and it moves based on the speed-power profile of the motor that governs the resistance rather than the actual speed reported by the bike trainer. Unfortunately our correspondence with Wahoo did not result to any additional information regarding the motor. We can only assume that in short term spans of roughly 1 second intervals that we receive data from the bike trainer over ANT+, the changes to the absolute wheel speed is not on the order that makes the assumption of linear power-speed curve invalid. We have not investigated this assumption further and we use data recorded using this sensor to calculate speed fluctuations in short time spans. Figure 2 shows the Wahoo Kickr Bike and the back wheel. We will refer to the Wahoo Kickr bike as bike trainer in the rest of the documentation.

Data from these sensors is aggregated by our communication board, which consists of an Arduino Uno R3 equipped with an Ethernet shield. We selected the Arduino platform for its cost-effectiveness and open-source hardware ecosystem. The board uses an ATmega328P micro-controller running at 16 MHz frequency. It utilizes interrupts to count the high-frequency pulses from the speed sensor and digital inputs to read the steering angle, processing both at a sampling rate of 50 Hz.

1.3 Software and Simulation Environment

The 3D visualization is powered by the CARLA Simulator, an Unreal Engine plugin for simulating autonomous driving scenarios. Although the main focus of the development team is on Autonomous driving applications, CARLA simulator environment can be used for certain driving and bicycling simulation scenarios. It provides a full 3D environment for building maps and library for different vehicles that can be used on the network. It also comes with some sample maps as starting point for some applications. According to official documentation, the road layouts are generated using RoadRunner (a MATLAB plugin) and enriched with 3D models in Unreal Engine. For a detailed guide on this map creation process, please refer to our virtual environment module tutorial.

CARLA comes with a Python API that we utilize to control the flow of the simulation. It also comes with sample python scripts that demonstrate different applications of the API. They serve as starting point for our simulation logic. Some of the scripts are used as they are shipped by developers, and for some we have used modified versions provided to us from the original TUM bicycle simulator. Our modifications are detailed more thoroughly in Section 4.2.

1.4 ANT+ Protocol and Licensing

A critical component of the system is the ANT+ connection, which allows the PC to control the bike trainer. Because ANT+ is a proprietary protocol developed by Garmin with strict licensing terms, we cannot publish the specific source code used for this connection. However, Section 4 of this document provides instructions for getting started with the ANT+ protocol and development.

2. Communication Board and Sensors

This section describes the communication board used to acquire sensor data and transmit it to the simulator PC. It outlines the hardware components, their function in the system, and the structure of the corresponding firmware. References to the relevant datasheets and to the repository containing the build instructions are provided where appropriate.

2.1 Hardware

To minimize development effort and cost, the system is built on standard Arduino boards. The Arduino platform offers an established development environment, straightforward prototyping, a large support community, and broad compatibility with open-source tools that fits the project’s goal of a modular, accessible bicycle simulator design.

The communication board is based on the Arduino Uno R3 (ATmega328P, 16 MHz) board. Its peripherals including USART, I²C, SPI, PWM, timers, counters, and external interrupts are more than sufficient for the simulators requirements. Our design intends to do minimal processing on the board: reading two sensors, applying simple processing, and transmitting the processed data to the simulator PC via Ethernet. The interrupt capability of the ATmega328P is particularly important for accurately capturing the wheel speed. Digital I/O pins are used to read out steering sensor values.

For ethernet communication functionality, an Arduino Ethernet Shield V1 (Wiznet W5100) is mounted on the Arduino board. It provides a 10/100 Mb/s Ethernet interface over SPI and provides a standard RJ45 connector for connecting the communication board and the simulator PC using standard Ethernet cables. The V1 shield is no longer widely available; however, the V2 version of the Ethernet Shield should be fully compatible because both versions rely on the same Ethernet.h and EthernetUdp.h libraries. Using the latest versions of these libraries will ensure compatibility of our provided Arduino code with the new version of the Ethernet Shield.

Wheel speed is measured using a HEDM-5500-B13 rotary encoder. It outputs two quadrature channels; only Channel A is used because reverse rotation is not captured. Each full revolution (360° degrees) produces 1024 falling edges on Channel A. These edges are captured through the external interrupt of the Arduino board. The encoder’s high resolution enables frequent short sampling windows of 2ms. Although this configuration exceeds the resolution strictly needed, it simplifies the signal processing and works reliably with a single digital input.

Steering angle is measured with an EMS22A50-D28-LT6 absolute rotary encoder. Unlike the incremental wheel encoder, this sensor reports an absolute angular position relative to a reference. It requires three micro-controller pins (data, chip-select, and clock) to read out the 10-bit value of the absolute rotary encoder in a synchronous serial format. 

For the detailed documentation regarding speed and steering sensor please refer to speed sensor documentation and steering sensor documentation.

2.2 Firmware

The firmware running on the communication board handles the basic tasks required to collect sensor data and transmit it to the simulator PC. It reads the wheel speed and steering angle sensors at regular intervals, converts these readings into processed values, and sends them over Ethernet in a simple and consistent format. The firmware is written to match the capabilities of the Arduino Uno and uses straightforward timing and hardware control routines. This provides a stable data link between the physical setup and the simulation without unnecessary complexity.

2.2.1 Sensor Readout

On the Arduino Uno R3, the onboard firmware manages sensor acquisition and network communication for the simulator. Its operation is based on three core tasks: measuring wheel speed, reading the steering angle, and transmitting both values to the simulator PC via UDP. These tasks are executed at a fixed rate of 50 Hz to ensure stable and predictable data flow into the simulation environment.

Wheel speed is measured using the HEDM-5500 incremental encoder connected to an external interrupt pin of the microcontroller. Each falling edge on Channel A represents a fixed angular increment of the rear wheel rotation. During a short and precisely timed sampling window, the firmware counts the number of falling edges that occur within 2 ms. This count is converted into a rotational speed value and then into linear speed. The short sampling interval keeps latency low and matches the update rate of the rest of the system. To avoid interference with other firmware tasks, the interrupt is enabled only during the measurement window and disabled immediately afterwards.

The steering angle is read from the EMS22A absolute rotary encoder using its synchronous serial output. The firmware manually controls the chip-select and clock lines and samples the data line bit by bit to reconstruct the 10-bit angular value. This readout task is executed at a frequency of 50 Hz, matching the wheel speed measurement rate. During the firmware initialization step, an initial measurement is taken to define the neutral handlebar position. This value is subtracted from subsequent readings and the result is converted into degrees. The calibration step requires the handlebars to be positioned perpendicular to the bicycle frame so that future readings report only the deviation from the neutral position.

The calculated speed and steering measurements are passed to a separate function whose sole task is to transmit these values to the simulator PC over Ethernet.

2.2.2 Network Communication

Network communication is handled by the Ethernet shield using the standard Ethernet.h and EthernetUdp.h libraries. During firmware initialization, the MAC address and the static IP address of the Arduion board are configured, a UDP socket is opened, and the device is prepared to send data to the simulator PC. Every 20 ms, after updated wheel speed and steering values are acquired, the firmware formats a short ASCII message containing both measurements and sends it as a UDP packet. The current implementation uses a comma-separated format, “speed,steering”. Speed is transmitted in m/s, and steering is transmitted in degrees relative to the calibrated zero position. To ensure that outdated data is not transmitted, the buffer is cleared and the packet is re-initialized in each transmission cycle.

2.2.3 Timing and Firmware Structure

The main program loop works as a simple timed scheduler. It uses the millis() clock function to check the elapsed time and starts one full acquisition, processing, and transmission cycle for both sensors every 20 milliseconds (equivalent to 50 Hz). The code avoids the use of delay() and keeps the system responsive while allowing accurate interrupt-based wheel speed measurement. The chosen cycle time balances sensor update needs and simulator requirements. The loop performs no additional logic, which keeps the firmware behavior predictable and consistent.

From a structural perspective, the firmware is intentionally kept minimal. The sensor-specific functions (calculateRpm() and readAbsEncoder()) handle their respective hardware interactions, while a single network function formats and transmits the data. This structure keeps the code easy to understand, maintain, and modify when new sensors, additional communication protocols, or tighter integration with other system components are needed. The firmware relies on the Ethernet, EthernetUdp, and SPI libraries and assumes the use of an Arduino Uno R3 with an Ethernet Shield V1 or V2.

A few constraints relevant for replication should be noted. The system assumes that all tasks complete within the 20 ms cycle. No error handling is implemented for UDP packet loss, invalid steering readings, or network initialization failures. When adapting the firmware to different encoders, alternative update frequencies, or network configurations, changes to pin assignments, processing coefficients, or timing parameters may be required.

2.2.4 Pseudocode

A concise overview of the firmware workflow is shown in Table 1.

 // — INITIALIZATION —
SETUP:
CONFIGURE pins for Speed_Sensor and Steering_Sensor
INITIALIZE Ethernet_Interface and UDP_Socket

steering_offset = READ Steering_Encoder_Value  // Set zero point 

// — MAIN CYCLE —
LOOP (Repeat every 20 ms):
// 1. Speed Measurement
interrupt_count = 0
ENABLE Interrupts
WAIT 2 ms
DISABLE Interrupts
speed = CALCULATE (interrupt_count -> rpm -> speed)

// 2. Steering Measurement
raw_steering = READ Steering_Encoder (10-bit)
steering_degrees = (raw_steering – steering_offset) CONVERT TO degrees

// 3. Data Transmission
message = FORMAT “speed,steering” values
SEND UDP message TO Simulator_PC
END LOOP
Listing 1: Pseudo-code demonstrating communication board code flow

2.2.5 Requirements and Practical Considerations

Several practical considerations should be taken into account when setting up this design or when implementing it on alternative hardware platforms. The current firmware is intentionally minimal and relies on the basic feature set of the Arduino Uno, using standard protocols available on low-cost microcontrollers. Alternative boards can be used as long as they support the required interfaces and hardware capabilities:

  • External interrupt support for reading incremental encoder sensors
  • SPI communication for interfacing with the absolute encoder and Ethernet connection
  • Ethernet connectivity, either built in or provided through a compatible shield or module
  • Sufficient processing resources (approximately a 16 MHz clock rate and at least 32 KB of flash memory)

Common alternatives with similar functionality include ESP32 boards, Adafruit microcontroller boards, and the Raspberry Pi Pico. Depending on the platform, some changes to the timing or communication logic may be needed. The firmware is written using standard Arduino libraries, so it needs to be adapted to the libraries of the target platform. Boards that support C/C++ development usually require fewer changes, although interrupt setup and peripheral configuration may differ between platforms.

When selecting or adapting a platform, basic electrical requirements also need to be considered. Both encoders operate at 5 V logic, and the Ethernet shield expects standard 5 V Arduino I/O levels. Boards that use 3.3 V logic, such as many ESP32 or RP2040 designs, may require level shifting or verification that their inputs are 5 V tolerant. Encoder wiring should be kept short and use shielded or twisted-pair cables where possible to reduce noise, especially on the incremental encoder interrupt line.

Some networking constraints also need to be considered. The firmware uses a static IP configuration, so the host computer and local network must be set up to accept and route packets from the assigned address. The simulator expects a continuous stream of UDP packets at 50 Hz, and packet loss or jitter introduced by Wi-Fi or high-latency links can reduce performance. For this reason, wired Ethernet is preferred.

The design works with both the Arduino Ethernet Shield V1, which uses the Wiznet W5100 controller, and the Arduino Ethernet Shield 2, which uses the newer W5500 controller. The official Arduino Ethernet library (version 2.0.2 and later) supports both controllers and is the recommended choice for maintaining compatibility across shield versions (https://docs.arduino.cc/libraries/ethernet/). Some third-party shields based on the W5500 use the separate Ethernet2 library, which is not compatible with W5100 hardware, so the controller type should be checked before choosing a library. In practice, using the standard Ethernet library provides consistent behavior on both official V1 and V2 Ethernet shields.

Boards with different architectures, such as dual-core processors, integrated Wi-Fi, or lightweight RTOS scheduling, may require changes to how the timing loop is implemented. The 20 ms cycle should remain consistent and uninterrupted, even when background services or concurrent tasks are running.

In summary, the design is flexible and portable, but electrical compatibility, library support, and timing behavior need to be checked when using non-Arduino or alternative Arduino hardware. These points help ensure that the data readout loop remains accurate and that the simulator receives stable and reliable data.

3. Connection to Wahoo Kickr Bike (over ANT+)

The previous sections describe how wheel speed and steering angle are measured using dedicated sensors mounted on the bike. This approach is required because the Wahoo Kickr Bike only broadcasts speed at a low rate and does not report steering. In practice, the ANT+ broadcast rate of the bike varies between about 0.8 and 1.33 Hz, which is too slow for real-time simulation and leads to visible lag and unstable steering. Our custom speed and steering sensors provide high-frequency data at 50 Hz using the communication board. The data from the Kickr Bike is used mainly for calibration and for accessing additional data fields provided by the trainer.

Through the ANT+ connection, the bike trainer transmits distance, total elapsed time, cadence, power, and speed. If a heart rate monitor is paired with the bike, heart rate data can also be received. These values are passed to the simulation so that it reflects the rider’s actual performance and the simulation remains consistent with the data reported by the bike trainer.

In addition to reading data, the simulator PC must send certain parameters to the bike trainer. At the start of each simulation session, the rider’s body weight is sent once so the trainer can adjust its internal dynamics model. During the simulation, the incline value is updated continuously so that the bike changes its pitch and resistance level to match the virtual terrain. This produces the characteristic frame tilting of the TiptoP bicycling simulator during climbs and descents. Unlike the one-way data flow between the Arduino board and the PC, the ANT+ connection with the bike trainer is bidirectional and supports real-time commands as well as the reception of ride data. The following subsections describe the hardware and software components required to set up this connection.

3.1 Hardware

To communicate with the ANT+ module of the bike trainer from the simulator PC, a compatible ANT+ USB dongle is required. These devices provide a dedicated ANT radio interface and are commonly used with virtual indoor cycling platforms. The dongle is connected to a USB port on the simulator PC. For TiptoP bicycling simulator the following ANT+ USB dongles were tested:

  • Moofit ANT+ Dongle USB ANT Stick (https://manuals.plus/asin/B0CNSM3ZK6)
  • Garmin USB ANT Stick™ (https://www.garmin.com/en-US/p/10997/)
Fig. 3.1: Moofit ANT+ Dongle USB ANT Stick

Both dongles worked reliably in our setup. They were used only on Windows 11, where the drivers were installed automatically. The linked pages provide manuals and driver resources in case manual installation is needed. We did not test compatibility with earlier Windows versions or with Linux, although these dongles are generally supported on both systems when the appropriate ANT USB driver is installed. Other ANT+ dongles that follow the standard USB ANT interface should also work, as long as the required driver is installed on the simulator PC.

3.2 Software

Access to technical details of the ANT+ protocol is restricted by licensing, so the full specifications cannot be reproduced here. To develop a custom ANT+ interface, registration for an ANT+ Adopter account at thisisant.com is required, which is the official source for ANT and ANT+ resources. Registration is free and provides access to protocol documents, ANT+ device profiles, and software development kits (SDKs) for multiple platforms.

The ANT+ SDKs are available for Windows, Linux, macOS, and Android. Each SDK includes libraries to create ANT channels, interpret ANT+ messages, and send control commands. Example programs are also provided to demonstrate device discovery, message handling, and channel management. For development without access to physical hardware, ANT+ simulation tools are available to emulate different ANT+ device profiles.

For indoor trainers such as the Wahoo Kickr Bike, the relevant specification is the Fitness Equipment (FE) Device Profile. This document describes the broadcast fields (speed, cadence, distance, power, and others) as well as the control messages needed to set resistance or incline. Used together, the SDK and the FE profile provide all information required to build a bidirectional ANT+ connection that receives real-time trainer data and sends incline or resistance commands back to the bike.

Figure 3: Internal communications between different software components of the simulator

3.3 Practical Notes

In general, ANT+ dongle hardware operates reliably, but a few practical considerations should be kept in mind. ANT+ uses the same 2.4 GHz frequency band as Bluetooth and older Wi-Fi standards, but at much lower transmission power. As a result, nearby devices operating on the same band can reduce signal quality. Placing the dongle close to the bike trainer and avoiding unnecessary sources of interference improves reliability.

Before developing or testing any software, the ANT+ dongle should be verified using a known working application. Free virtual cycling platforms such as MyWhoosh can be used to confirm that the ANT+ hardware is functioning correctly. If problems occur, installing the latest firmware or drivers for the dongle is recommended as a first step.

During demonstrations or longer testing sessions, keeping a second ANT+ dongle available helps avoid interruptions if one unit fails or becomes unreliable. Verifying that the hardware works independently of your developed code simplifies debugging and prevents hardware issues from being mistaken for software errors.

3.4 Note On ANT+ Protocol

Garmin has discontinued the ANT+ Product Certification Program and stopped creating new device profiles as of June 2025 (https://www.thisisant.com/business/go-ant/ant-brand/). This change does not affect existing products such as the Wahoo Kickr Bike, and current ANT+ device profiles remain available for use. However, it indicates that the ANT+ ecosystem will no longer expand and that future development is unlikely to focus on this protocol. For more information regarding this issue please visit : [https://www.dcrainmaker.com/2025/01/the-begining-of-the-end-for-ant-wireless.html]

ANT+ has been a reliable protocol for fitness data and basic trainer control, but its documentation and development tools require registration and are less open than more recent alternatives. In contrast, Bluetooth Low Energy (BLE) is widely used, partly because it is supported across all major operating systems, including iOS. BLE also benefits from a broad ecosystem of development libraries, including several Python packages that simplify code development.

The bleak library (https://bleak.readthedocs.io/en/latest/) provides a cross-platform interface for managing BLE connections, and higher-level packages such as pycycling (https://pypi.org/project/pycycling/) and PyFTMS (https://pypi.org/project/pyftms/) build on this foundation to offer device-specific support for cycling trainers and general fitness equipment. These libraries help reduce development effort and improve flexibility across different platforms.

4. Simulator PC and Software

The simulator PC runs the 3D simulation software that models urban roads, vehicles, and the surrounding environment. It receives data from both the communication board and the bike trainer, and it sends control commands back to the bike trainer. Hardware requirements depend strongly on the simulation software being used, as well as on practical factors such as screen resolution, frame rate, number of displays, size of the simulated area, number of simulated road users, and any use of AR or VR. For this reason, the choice of simulation software affects the hardware budget and should be considered early in the design process.

The following subsection gives an overview of the hardware configuration used for the simulator PC, followed by a description of the software stack and the code used to control the simulation.

4.1 Hardware

The system uses the CARLA Simulator (https://carla.org/) as the basis for the simulation environment. According to the CARLA documentation (https://carla.readthedocs.io/en/latest/start_quickstart/), the minimum system requirements are:

  • Operating System: Windows 10 or 11, or Ubuntu 20.04 or 22.04
  • Graphics Card: Dedicated GPU equivalent to an NVIDIA RTX 2070 with 8 GB of VRAM
  • Disk Space: 20 GB
  • Two TCP Ports: 2000 and 2001 (default)

To ensure stable performance and allow for future extensions, a higher-end hardware configuration was selected for TiptoP bicycling simulator.

  • Operating System: Windows 11 Pro
  • Processor: Intel Core i9-13900K (24 cores)
  • Graphics Card: RTX 4090 with 24 GB GDDR6X VRAM
  • Memory: 64 GB DDR5 (2 × 32 GB) at 5600 MHz, CL28
  • Storage: 4 TB total (2 × 2 TB M.2 NVMe)
  • Power Supply: 1300 W

With this configuration, no hardware-related bottlenecks have been observed. All performance limitations identified are software-related.

To connect the communication board via Ethernet, the simulator PC must also be connected to a router or network adapter. Basic USB-to-Ethernet adapters are sufficient, but the following router was used in our setup:

  • Router: EDIMAX BR-6428NS V5 WLAN Router, 2.4 GHz, 300 Mbps

The ANT+ dongles used to communicate with the bike trainer are connected directly to the USB ports of the simulator PC.

4.2 Software

CARLA is an open-source simulation platform designed to support the development, training, and validation of autonomous driving systems. It provides open digital assets, including maps, buildings, and vehicles, together with a flexible API that allows full control over static and dynamic actors, sensor configurations, and environmental conditions (https://carla.org/). These features enable the creation of detailed and configurable 3D maps, which are used as the basis for rendering the virtual environment of the TiptoP bicycling simulator.

CARLA includes sample Python scripts that demonstrate how to interact with the simulator through its Python API, and these scripts serve as the starting point for our workflow. Earlier work at TUM adapted these scripts for a bicycle simulator use case, and this work was extended by adding support for 3D maps with elevation information and real-time gradient calculation to reproduce climbing and descending conditions accurately. Further details on the development of 3D maps with gradient information are provided in the corresponding documentation.

The scripts listen directly on the Ethernet interface to receive sensor data from the communication board. Interaction with the bike trainer requires an additional interface built using the Garmin ANT+ SDK described in the ANT+ section of this documentation. This interface reads sensor values from the trainer and sends control commands generated from the 3D environment and rider position, along with the required setup commands.

4.2.1 CARLA Simulator

CARLA was not developed specifically for human-in-the-loop simulation, but it can be adapted for car or bicycling simulator applications. Not all applications and scenarios can be simulated accurately using CARLA simulator, and some limitations should be expected. For new developments, the advantages and limitations of CARLA should be evaluated carefully and compared with other available simulators for the intended use case and scenarios. This comparison is not included here, as new simulation platforms appear regularly and such an evaluation remains an initial step in the design of any new bicycling simulator.

Some issues encountered during the development of TiptoP bicycling simulator are noted here. The main concern is that the CARLA simulator is still not a finished product, as also indicated by its versioning number. Although it was first announced in 2017 (see the original paper at https://proceedings.mlr.press/v78/dosovitskiy17a/dosovitskiy17a.pdf), the latest release remains at version 0.9.16. Development has progressed in intermittent phases, with periods of rapid and slower feature additions. The focus of new features has also shifted over time, from autonomous driving functionality and benchmarks, to improved graphics quality, and more recently to the integration of generative AI modules for enhanced non-playable character (NPC) behavior.

The current scope of features has resulted in more than 1100 open issues on the CARLA GitHub repository at the time of writing, with many of these expected to be addressed in a future version 1 release. During development, many issues were encountered, including low frame rates, intermittent freezing of the 3D simulation, problems when packaging maps created by our team, dependency conflicts, incorrect lines of code in some files, and limited support for newer Linux versions (for example, Ubuntu is officially supported only up to version 22.04). Sufficient development time should therefore be allocated to handle such issues if CARLA is selected for a new bicycling simulator project. Some problems and corresponding solutions are described later in this documentation, but not all issues can be covered. The CARLA GitHub repository (https://github.com/carla-simulator/carla) and Discord server (https://discord.com/invite/8kqACuC) are useful resources for reporting bugs and obtaining support from the development team and user community.

CARLA can be executed in three configurations: running the compiled standalone version (installation instructions are available at https://carla.readthedocs.io/en/latest/start_quickstart/), building it from source (https://carla.readthedocs.io/en/latest/build_carla/), or running it as a Docker container (https://carla.readthedocs.io/en/latest/build_docker/). The Docker option was not tested in our setup and is therefore not discussed further. A detailed step-by-step installation guide for the approaches used in this project is available here: (https://bicyclesimulator.onrender.com/docs/Setup_and_Building_of_Simulator/Computer_Setup).

The compiled standalone version can only be used when the 3D maps are packaged. Building from source compiles CARLA as an Unreal Engine plugin and allows the creation and modification of custom 3D maps. A detailed guide for creating maps, including hilly and uneven terrain, is available here: https://bicyclesimulator.onrender.com/docs/Setup_and_Building_of_Simulator/Setup_of_Scenarios.

Once the 3D maps are created, they can in principle be packaged for use with the standalone version (see https://carla.readthedocs.io/en/latest/tuto_content_authoring_maps/#exporting-a-map-as-part-of-a-complete-carla-package). In our case, the packaging process was not completed successfully, so the built version of the CARLA simulator was used for all experiments.

Running the built version has several drawbacks. The simulator must be built each time the PC is started, which slows the start up time of the simulator. In addition, build-related issues occasionally caused intermittent freezing of the simulation at intervals of around 9–10 seconds. These issues occurred without changes to the simulator configuration and could only be traced back to the build process. As a result, using the built version is less reliable, and packaging maps for use with the standalone version is recommended whenever possible. The standalone version can also be expected to require fewer system resources than the built version as it does not require the large array of tools for editing maps provided by the Unreal Engine.

For the remainder of this documentation, the focus is on the functionality added by our team to the scripts provided by the CARLA development team.

4.2.2 CARLA Terminology

Some terminology from the CARLA simulator is introduced here (from the official Carla Documentation). The following list is not exhaustive. Further details on these concepts are available at https://carla.readthedocs.io/en/latest/core_concepts/.

I. Fundamental Architecture

  • Server: The main simulator instance. It runs the physics, renders the graphics (if enabled), and handles the logic of the environment (traffic, weather).
  • Client: The script (usually Python) that you run to communicate with the Server. It sends commands (e.g., “spawn a car here”) and receives data (e.g., “give me the camera image”).
  • World: An object representing the simulation. It acts as the central hub to spawn actors, change weather, and get the current state of the simulation. There is only one World object active at a time.
  • Synchronous Mode: A simulation mode where the Server waits for a “tick” (signal) from the Client before moving to the next frame. This ensures the simulation and your code stay perfectly in sync, which is critical for collecting training data.
  • Asynchronous Mode: The default mode where the Server runs as fast as possible, regardless of whether the Client is keeping up. Useful for real-time testing but can lead to data loss if the Client is slow.
  • Traffic Manager (TM): A module that controls vehicles in “Autopilot” mode. It handles city traffic rules, intersection negotiation, and lane changing automatically so you don’t have to code behavior for every background car.

II. Actors & Content

Objects that exist and “live” inside the simulation.

  • Actor: Any dynamic object in the simulation. This includes Vehicles, Walkers (pedestrians), Sensors, Traffic Signs, and Traffic Lights.
  • Blueprint: A “template” or recipe used to create an Actor. Before you spawn a car, you must select its Blueprint (e.g., vehicle.tesla.model3). Blueprints contain attributes you can modify, like color or sensor resolution.
  • Blueprint Library: The collection of all available Blueprints provided by CARLA.
  • Spectator: A special Actor that represents the “camera” view of the simulation window. You can move the Spectator to change your view of the world without affecting the actual simulation.
  • Walker: The term used for pedestrians in CARLA. They can be controlled by a specific AI controller (WalkerAIController) to wander around randomly.

III. Map & Navigation

Terms related to the static environment and how vehicles find their way.

  • Map: The representation of the town (e.g., “Town01”, “Town10HD”). CARLA maps use the OpenDRIVE standard (file format .xodr) to define road geometry.
  • Waypoint: A specific 3D point directed along a lane. Vehicles use lists of waypoints to navigate. A waypoint contains information about the lane width, type, and strictly defines where a car “should” be driving.
  • Junction: An area where roads intersect (an intersection). Lane topology is more complex here to allow for turning.
  • Landmark: Static signals defined in the OpenDRIVE map, such as stop signs, yield signs, or speed limits. (Note: The physical 3D object of a sign is an Actor, but the logical rule is a Landmark).
  • Spawn Point: Recommended locations (transforms) on the map specifically designed for spawning vehicles so they don’t overlap or spawn inside a building.

IV. Sensors

Devices attached to vehicles to collect data.

  • RGB Camera: Provides standard visual images, similar to a real-world camera.
  • Depth Camera: Provides an image where pixel color represents the distance of objects from the camera.
  • Semantic Segmentation: A “ground truth” camera that colors every object based on its class (e.g., all cars are blue, all pedestrians are red, road is purple). Crucial for training AI models.
  • LiDAR: Simulates a rotating laser scanner, producing a point cloud of the environment.
  • GNSS (GPS): Provides the geolocation (latitude, longitude, altitude) of the actor.
  • IMU: Inertial Measurement Unit; measures accelerations and rotation rates.
  • Collision Sensor: A special sensor that triggers a “callback” event only when the parent vehicle crashes into something.

These terms are used in the following sections when explaining the scripts and the added functionality.

4.2.3 Understanding CARLA server/client architecture

CARLA uses a server–client architecture that separates 3D rendering from control logic. The CARLA server is built on Unreal Engine 4 and is responsible for simulating the physical world, managing actors such as vehicles and pedestrians, and rendering the 3D environment. When the server is running, it opens a spectator window that allows the simulation to be viewed.

The CARLA client is typically implemented as a Python script using the CARLA API. The client sends control commands, such as spawning vehicles at specific locations or changing weather conditions, and receives sensor data from the server, including camera images or LiDAR point clouds, for further processing.

The connection between the server and client is established over TCP sockets, with port 2000 typically used for general RPC (Remote Procedure Call) commands. A secondary port, usually port 2001, is used for high-speed streaming of sensor data to prevent image data from interfering with command communication. This architecture allows flexible deployment, as the CARLA server can run on a machine with a high-end GPU while the Python client runs on a separate system or multiple machines. Multiple clients can also connect to a single server at the same time, enabling separate scripts to manage traffic, control an ego vehicle, or handle other simulation tasks independently. In the TiptoP bicycling simulator, both the CARLA server and all client scripts run on the same simulator PC.

Figure 4: Diagram showing multiple clients connecting to one instance of a CARLA server (https://carla.readthedocs.io/en/0.9.8/start_introduction/)

4.2.4 Script structure

CARLA provides a set of example scripts that are included with the package, either in the standalone or built version, and are located in the PythonAPI/examples directory of the respective CARLA installation or build folder. To use these examples, the dependencies listed in the requirements.txt file must first be installed. Not all scripts are required for this project, and the focus is limited to the scripts listed below. All of these scripts communicate with the CARLA simulator through the Python API.

  • manual_control.py: Contains the main() procedure. It initializes the simulation and runs the main loop. Command line interface (CLI) parameters are parsed and passed to the relevant classes and methods.
  • BikeSensor.py: Sensor input module: Receives sensor data from the Arduino communication board via Ethernet and from the bike trainer via the ANT+ protocol (over a local Ethernet port connection to the ANT+ connection module). It processes, stores, and logs the received data.
  • CameraManager.py: Camera handling and rendering helper attached to the player actor. It supports multiple camera sensors for multi-screen simulation setups.
  • VehicleDynamics.py: Governs the dynamics models, such as VehicleDynamicsSingleTrack and VehicleDynamicsKeyboard. These dynamics are overridden, as the simulator uses the dynamics model provided by the bike trainer.
Figure 5: Flowchart of the game_loop() function

The manual_control.py module was originally provided to allow users to drive a vehicle within a loaded map using input controls such as a keyboard, similar to a driving game. In this project, the module is reused by replacing keyboard input with steering and speed data obtained from the bicycle sensors.

Execution starts in the main() function, which reads command line arguments, loads configuration files if provided, sets the display resolution and window size, and initializes other runtime parameters. It then starts the game_loop() function, where the simulation runs continuously until it is interrupted by a KeyboardInterrupt exception.

An additional command line argument, -x or –spawn-point-index, was introduced to specify the spawn point index within the map. This allows selection of a specific starting position for the ego bicycle when multiple spawn points are defined and one must be chosen at the start of the simulation.

The game_loop() function is the main execution loop of the simulation. It begins by initializing a pygame instance for displaying the simulation and uses the arguments parsed in main() to configure parameters for the objects created within the loop. A CARLA client object is then created, followed by initialization of a BikeSensor object (defined in BikeSensor.py) to handle communication. The pygame display mode is set at this stage.

Next, a world object is created using the CARLA client, along with a controller object from the DualControl class, and a clock is initialized. The simulation then enters an infinite loop in which the clock is updated, the world state advances, the scene is rendered, and the pygame display is refreshed. Within this loop, controller events are processed, and if an abort request is detected, the loop terminates and the world and related objects are properly destroyed. Figure 6 shows a selected subset of classes, methods and connections that make up the structure of the simulation.

The general flow of the simulation is outlined here. Only the major additions and modifications made to these classes are discussed, without detailing the internal workflows of each individual class. A full explanation of these modules is outside the scope of this documentation. More can be found in the comments within the code. Please refer to our code base here: https://github.com/buw-bicycle-traffic/CARLA-Bicycling-Simulator-Scripts .

Figure 6: Selected class diagram of the scripts for TiptoP bicycling simulator

World Class (in manual_control_simulation.py ):

The following modfication were made to the world class:

  • In the init() function: a line was added to select the bicycle model from the blueprint library (“vehicle.diamondback.century”).
  • In the restart() function: a line was added to spawn the bicycle at a specified spawn point when the spawn point is provided as a command line argument.

Two additional methods were added to the World class to compute the ground z-coordinate and the road gradient at a given location:

  • Calculate_ground(self, location): This method uses the cast_ray(self, initial_location, final_location) function provided by the CARLA Python API. The ray is cast from the given location to a second location with the same x and y coordinates and a z value reduced by 100, which is a sufficiently large distance below the original point. The function returns intersections with any geometry along this line. The first intersection point is treated as the ground location. Since the x and y coordinates remain unchanged, the method effectively determines the z-coordinate of the ground at the given position.
  • calculate_grade(self, location, direction, distance=1.0): This method takes a location and a direction vector, where the direction vector corresponds to the forward movement direction of the bicycle. Starting from the given location, two points are computed at a fixed distance on the left side of the bicycle along this direction vector. The ground positions for these two points are obtained using the calculate_ground() method. The road grade is then calculated as the slope of the line connecting the two ground contact points. A lateral offset of 1 m to the left is assumed to avoid interference from the bicycle geometry and to reduce disturbances in the calculation.

DualControl Class:

  • _parse_vehicle_controller_input(self, bike_sensor, clock, world) function: This function reads the speed and steering values from the bike_sensor object. It retrieves the current bicycle location, computes the road grade, and limits the grade to the range supported by the bike trainer. The calculated value is then passed to the set_grade() function of the bike_sensor object to update the trainer incline. Finally, the speed and steering values are forwarded to the vehicle_dynamics_tick object.
Figure 7: Communication between Python Script, bike trainer and communication board 

BikeSensor class (in BikeSensor.py):

BikeSensor.py was originally written for the TUM bicycle simulator to receive speed and steering data from an Arduino module. It was modified to work with the Arduino implementation used in TiptoP bicycling simulator, and an additional function was added to it to receive data from the bike trainer via the ANT+ protocol. Functionality to set the incline of the bike trainer was also added, along with logging support to record all data received from both the Arduino communication board and the bike trainer. Figure 7 shows the connection and data flow between BikeSensor, ANT+ Interface, the communication board and the bike trainer.

  • __init__(…): This function initializes two communication threads that run in parallel to record data from both sources ( communication board and bike trainer ) without interfering with each other. To simplify handling of ANT+ data, the ANT+ Module ( Written in C++ ) is configured to open a local port on the simulator PC for communication with the BikeSensor class. Both threads are started to connect to their respective ports and receive data. Configuration of the ports, initialization of the communication threads, and setup of all required variables are handled in this function. Log files are also opened, their headers are written using the write_log_header() function, and the system is prepared for data logging.
  • _arduino_loop(self): This function runs a loop that receives speed and steering data from the Arduino port. The received data are processed and calibrated. Speed calibration is performed using a reference speed obtained from the ANT+ source at intervals of approximately 0.75–1.25 s. The processed values are stored in the corresponding variables, and the Arduino data, together with their reception timestamps, are logged.
  • _ant_loop(self): This function operates in a manner similar to _arduino_loop(). It continuously receives speed, cadence, power, elapsed time, distance traveled, and heart rate data from the bike trainer. Availability of heart rate data depends on whether a heart rate sensor is paired with the bike trainer. The received values are logged, and the current incline value is sent through the same socket connection to the C++ interface that communicates with the bike trainer.
  • Getters and setters for critical values, a low-pass filter, and a stop function to properly close the threads are also implemented. These components are straightforward and are not described in further detail.

VehicleDynamics.py

This script is part of the original CARLA example set and provides functionality for vehicle control using the keyboard. It includes the base class for vehicle dynamics. The class VehicleDynamicsSingleTrack(VehicleDynamics) was added as part of the TUM bicycle simulator.

In the current setup, the bicycle trainer provides its own internal dynamics model. The data received from the trainer are therefore used directly, and the single-track model is not applied. Although the single-track model showed only minor differences compared to the measured values, using the trainer-provided dynamics avoids additional processing and calibration. A dedicated vehicle dynamics model is thus not required. The inclusion of road inclines, which are not supported in the original single-track model, would also increase calibration complexity. Minor modifications were made to the VehicleDynamicsSingleTrack() class to bypass the dynamics calculations.

4.2.5 SUMO Co-Simulation in CARLA

CARLA supports SUMO co-simulation starting from version 0.9.9. SUMO provides more comprehensive traffic control than CARLA’s internal traffic manager, which primarily handles local decisions such as random intersection choices. With SUMO, route planning can be performed at the network level, and its routing tools can be used when simulations require planning based on road topology or traffic demand. Additional information on the CARLA traffic manager can be found at https://carla.readthedocs.io/en/0.9.15/adv_traffic_manager/#traffic-manager. Documentation specific to SUMO co-simulation in CARLA version 0.9.15 can be found here: https://carla.readthedocs.io/en/0.9.15/adv_sumo/.

When working with other CARLA versions, the corresponding version should be selected in the documentation. At the time of writing (late 2025), no documentation is available for SUMO co-simulation in CARLA versions based on Unreal Engine 5.5, which indicates that this functionality has not yet been implemented for those releases.

4.2.4.1 Practical Notes and Current Limitations

Traffic light synchronization between CARLA and SUMO does not work reliably. This limits the use of co-simulation in scenarios that depend on traffic lights. It is recommended to first run the examples provided in the SUMO co-simulation documentation to understand and assess the current state of co-simulation functionality.

The CARLA simulator contains a number of unresolved bugs, and the SUMO co-simulation inherits some of these issues. The integration was developed independently of the SUMO team at DLR, so performance and stability problems can occur. In our setup, the co-simulation could not be run on Ubuntu, but it was successfully executed on Windows after modifying one of the files described below. Pedestrian co-simulation is not implemented, and this functionality is therefore limited to scenarios that include only vehicles.

Traffic light management can be handled by either SUMO or CARLA. When SUMO is selected as the traffic light manager, the visual state of traffic lights in CARLA does not update accordingly. No documented parameters are available to resolve this issue, so manual workarounds or alternative approaches are required in scenarios where correct traffic light visualization is important. Traffic light states can only be controlled through the Python API, and in cases such as actuated traffic lights, the signal cycles must be updated manually. Guidance on traffic light control is provided in the CARLA documentation at https://carla.readthedocs.io/en/latest/core_actors/#traffic-signs-and-traffic-lights .

Note: To enable synchronization on Windows, modify the following file as described in https://github.com/carla-simulator/carla/issues/6072. Listing 2 shows the commented line from the original file and the line that should replace it.

File: Co-Simulation/Sumo/sumo_integration/sumo_simulation.py, line 304:

# sumo_net = traci.sumolib.net.readNet(net_file)

sumo_net = sumolib.net.readNet(net_file)

Listing 2: One-liner Change required for running of CARLA Co-Simulation

The general steps required to set up a co-simulation scenario in CARLA are outlined briefly below to provide an overview of the process. For detailed descriptions of parameters and individual steps, refer to the documentation at https://carla.readthedocs.io/en/latest/adv_sumo/:

  1. Generate CARLA vehicle types: Run create_sumo_vtypes.py to convert CARLA vehicle blueprints into SUMO-compatible vehicle types. The script requires a running CARLA instance and uses a small configuration file (vtypes.json), which can be modified if vehicle attributes need to be adjusted.
  2. Create the SUMO network: Use netconvert_carla.py to generate a SUMO network from CARLA’s OpenDRIVE map. This produces a .net.xml file, which can be further edited in SUMO’s NETEDIT if needed. A .sumocfg configuration file is then created to reference the network and route files.
  3. Start synchronization: Launch the co-simulation using run_synchronization.py, which runs CARLA and SUMO in lockstep. The script manages time-step synchronization and the exchange of vehicle state information between the two simulators.
  4. Spawn SUMO-controlled NPCs: Use spawn_npc_sumo.py to populate CARLA with background traffic managed by SUMO. The script relies on the prepared SUMO network and route definitions to spawn vehicles whose motion is synchronized back into CARLA.