# Jonny Boi

The mini-PC running in the rover

# Overview

Jonny Boi (official name: Jetson Orin Nano) is the mini-PC that runs most software in the rover. The rover also contains some software in the microcontrollers, which are the responsibility of Embedded. Jonny and all microcontrollers are connected through a switch and form a LAN. This LAN is also connected to the Ubiquiti router that communicates with the Base station.

Jonny Boi runs [NVIDIA Linux 36.4.4](https://developer.nvidia.com/embedded/jetpack-sdk-621), which is Ubuntu Jammy 22.04 with NVIDIA packages. On top of that, we install [ROS2 Humble Hawksbill](https://docs.ros.org/en/humble/index.html) and [Nav2](https://docs.nav2.org/).

All code running on Jonny Boi is stored in [https://github.com/RoboTeamTwente/erc-software-rover](https://github.com/RoboTeamTwente/erc-software-rover).

## Software Architecture

[![Software Architecture v5.excalidraw.png](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/software-architecture-v5-excalidraw.png)](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/2026-03-31-software-architecture-v4-2-excalidraw.png)

<p class="callout info">This image's official place is [on the drive](https://drive.google.com/drive/folders/1jQs3EzIct_fNm2hewfAxjQsyM8DRpr6w?usp=drive_link). Please keep them up-to-date.</p>

<p class="callout info">To edit this image, download it from the drive and open it at [https://excalidraw.com](https://excalidraw.com). When exporting, don't forget to toggle the "embed the scene" switch and upload it to the drive.</p>

This picture lists all software running on the rover and how data flows among different programs.

#### Communications

99% of our programs are running within the ROS2 framework and use the built-in publish-subscribe system to communicate. For more details, see [this tutorial](https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Topics/Understanding-ROS2-Topics.html "Understanding topics — ROS 2 Documentation"). TL;DR: The programs that have the data publish it to various topics, and other interested programs subscribe to that topic to receive the data.

For communications with programs running outside ROS2, we use Ethernet and protobufs. See [Communication system](https://bookstack.roboteamtwente.nl/books/communication-system "Communication system") for details.

#### Subsystems

<p class="callout warning">TODO</p>

# Getting Started

## Prerequisites

<span style="white-space:pre-wrap;">All development happens on </span>[Ubuntu 22.04 (Jammy Jellyfish)](https://releases.ubuntu.com/jammy/)<span style="white-space:pre-wrap;">. If you are running Windows, the easiest way to install it is </span>[through WSL2](https://learn.microsoft.com/en-us/windows/wsl/install).

<span style="white-space:pre-wrap;">If you prefer to work in Docker, you can use image </span>`<span class="editor-theme-code">docker.io/osrf/ros:humble-desktop-full</span>`.

<span style="white-space:pre-wrap;">You need to have a basic understanding of ROS2. Follow </span>[ROS2 tutorials](https://docs.ros.org/en/humble/Tutorials.html)<span style="white-space:pre-wrap;"> before you continue.</span>

## First time setup

<p class="callout info"><span style="white-space:pre-wrap;">See </span>[README.md](https://github.com/RoboTeamTwente/erc-software-rover/blob/main/README.md)<span style="white-space:pre-wrap;"> for more up-to-date instructions.</span></p>

<p class="callout danger">Watch out! NVIDIA “helpfully” keeps packages more up-to-date than stock Ubuntu, so e.g. OpenCV's version will be fresher on Jetson, compared to your development laptop.</p>

```bash
### Download the code
git clone https://github.com/RoboTeamTwente/erc-software-rover
cd erc-software-rover
git submodule update --init --recursive

### Launch ROS2 in Docker (only if you don't have Ubuntu 22.04)
#docker run --rm -it -v $PWD:/src -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY osrf/ros:humble-desktop-full
#cd /src

### Install dependencies
apt update
rosdep install -iy --from-path src

### Compile RealSense depth cam driver with custom flags
# It is a good time to grab some tea.
# Compilation will take about 5 minutes on a reasonably modern laptop,
# or about 30 minutes on Jonny Boi.
colcon build --packages-select librealsense2 --cmake-args \
  -DCHECK_FOR_UPDATES=OFF                                 \
  -DFORCE_RSUSB_BACKEND=ON
```

`<span class="editor-theme-code">librealsense2</span>`<span style="white-space:pre-wrap;"> is built from source to switch it to the </span>`<span class="editor-theme-code">RSUSB</span>`<span style="white-space:pre-wrap;"> backend. We use this backend because it does not require a kernel patch, unlike the default one. See the </span>[RealSense installation guide](https://github.com/realsenseai/librealsense/blob/9a0dd70db1a2c180b69c6c257cd2ee6120505499/doc/installation_jetson.md)<span style="white-space:pre-wrap;"> for details. We also disable the update checker, because it fails to build for some reason.</span>

## Common operations

### Compile

While working on a package, you'll frequently want to compile it and all its dependencies:

```bash
colcon build --packages-up-to=$PACKAGE
source install/local_setup.bash
```

<p class="callout warning"><span style="white-space:pre-wrap;">Don't forget to </span>`<span class="editor-theme-code">cd</span>`<span style="white-space:pre-wrap;"> to the root of the repo!</span></p>

<p class="callout info">`<span class="editor-theme-code">local_setup.bash</span>`<span style="white-space:pre-wrap;"> doesn't need to be sourced most of the time.</span></p>

### Run your code

<span style="white-space:pre-wrap;">There are two different things you can run in ROS2: A node, and a launch file. For more details see </span>[Understanding nodes](https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html)<span style="white-space:pre-wrap;"> and </span>[Launching nodes](https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Launching-Multiple-Nodes/Launching-Multiple-Nodes.html).

```bash
ros2 run $PACKAGE $EXECUTABLE
# -- or --
ros2 launch $PACKAGE $LAUNCH_FILE
```

# Business Logic

# Overview

<span style="white-space: pre-wrap;">Business Logic is encoded in </span>[Behavior Trees](https://arxiv.org/pdf/1709.00084 "Behavior Trees in Robotics and AI: An Introduction")<span style="white-space: pre-wrap;"> (do check out the link, it's the nicest introduction to BTs there is). We use </span>[BehaviorTree.CPP](https://www.behaviortree.dev/)<span style="white-space: pre-wrap;"> library for this. All business logic lives in the </span>[rtt\_behavior package](https://github.com/RoboTeamTwente/erc-software-rover/tree/main/src/rtt_behavior).

<span style="white-space: pre-wrap;">There's a bunch of BTs sketched out in the </span>`<span class="editor-theme-code">docs/business-logic.cts</span>`<span style="white-space: pre-wrap;"> file (</span>[in the same repo](https://github.com/RoboTeamTwente/erc-software-rover/blob/main/docs/business-logic.cts)<span style="white-space: pre-wrap;">). You can open it in </span>[TreeSheets](https://strlen.com/treesheets/).

<p class="callout warning">None of this code was ever run in production, just FYI</p>

# Computer Vision

# Overview

<span style="white-space: pre-wrap;">The computer vision subsystem of the rover is utilized in the Navigation, Surface Sampling and Maintenance Tasks of ERC. It is accomplished through the use of the rover cameras: Gripper camera (digital Arducam), Bottom chassis camera (digital Arducam), Front chassis camera (Depth). The subsystem can be further divided in two sections, ArUco Detection and Object detection, both aiding in different parts of the ERC competition. </span>

On the RoboWiki, please read the page "ArUco" for understanding ArUco Detection, and "YOLO" for understanding general object detection through the YOLO framework (such as detecting a button on the maintenance board).

###   

# ArUco

## Architecture and ERC tasks

<table border="1" class="align-right" id="bkmrk-aruco-marker" style="border-collapse: collapse; border-width: 1px; border-style: hidden;"><colgroup><col style="width: 100%;"></col></colgroup><tbody><tr style="border-style: hidden;"><td>[![](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/scaled-1680-/5AWimage.png)](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/5AWimage.png)

ArUco marker

</td></tr></tbody></table>

During the navigation task, there will be some landmarks of note that the rover can use to establish its position relative to the Mars Yard. These landmarks will be identified through fixed wooden poles that display an ArUco tag to identify them. The ArUco tags can be thought of as special QR codes that embed identifying numbers within their pixel arrangement. Read more about ArUco tags in the first paragraphs of this OpenCV documentation page: [https://docs.opencv.org/4.8.0/d5/dae/tutorial\_aruco\_detection.html](https://docs.opencv.org/4.8.0/d5/dae/tutorial_aruco_detection.html)

<span style="white-space: pre-wrap;">Thanks to a combination of the OpenCV library and the ROS2 TF2 framework, it is possible to extract the coordinates of these ArUco tags by using any type of camera. The above link for ArUco detection is a great starting point and much of its implementation has been reflected into the rover's code as well. For information regarding TF2, it is advisable to read the ROS2 documentation: [https://docs.ros.org/en/humble/Tutorials/Intermediate/Tf2/Introduction-To-Tf2.html](https://docs.ros.org/en/humble/Tutorials/Intermediate/Tf2/Introduction-To-Tf2.html)</span>

<table border="1" class="align-right" id="bkmrk-aruco-landmark" style="border-collapse: collapse; border-width: 1px; border-style: hidden;"><colgroup><col style="width: 100%;"></col></colgroup><tbody><tr><td class="align-center">[![image.png](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/scaled-1680-/MtXimage.png)](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/MtXimage.png)

ArUco landmark

</td></tr></tbody></table>

<span style="white-space: pre-wrap;">The obtained coordinates from the ArUco Detection and Pose Estimation process through the 'aruco\_track' will be then passed to the behavior node of the [ROS2 architecture ](https://bookstack.roboteamtwente.nl/books/jonny-boi/page/overview "Overview")where they will become input data for appropriate localization and Odometry calibration. Therefore, these coordinates will pass through the nodes Behavior, Planner, Smoother, Controller within the Jetson to determine an optimal navigation path. Once the path is determined, the appropriate driving commands will be passed through a published topic onto the Communications node and handed down into the appropriate hardware boards. This will be accomplished with the use of [Protobufs](https://bookstack.roboteamtwente.nl/books/communication-system/page/arm-board-protobuffers "Protobufs") .</span>

### <span style="white-space: pre-wrap;">Running the aruco\_track node</span>

<p class="callout warning"><span style="white-space: pre-wrap;">For now we use OpenCV version 4.8 and we are forced to build it from source, as you will read below. However, soon this might change to use NVIDIA packages instead, reducing build time by a lot.</span></p>

<span style="white-space: pre-wrap;">The ArUco detection and pose estimation code is placed in the aruco\_track node of the "erc-software-rover" Github repository. The README.md explains how to run this ROS2 node, while the main code implementation is in "src/aruco\_track/src/aruco\_tf2\_node.cpp". The files "CMakeLists.txt" and "package.xml" are responsible for project initialization and dependency installation (as per ROS2 standards; [Please read all ROS2 tutorials](https://bookstack.roboteamtwente.nl/books/jonny-boi/page/overview "Overview")). </span>

#### <span style="white-space: pre-wrap;">Step 1 - Build OpenCV from source</span>

<span style="white-space: pre-wrap;">Before doing anything else, make sure to build OpenCV 4.8 from source. We have a bash file that will do that for you in "/assets/install\_deps.sh", so run (needs to run only once, but it will take at least 20 minutes to fully install):</span>

```bash
./src/aruco_track/assets/install_deps.sh
```

However, note that the current implementation depends on OpenCV 4.8 specifically based on the CMakeLists.txt lines:

```c++
find_package(ament_cmake_auto REQUIRED)
find_package(OpenCV 4.8 REQUIRED)
```

So, if you will change OpenCV version from anything other than 4.8 in the future, make sure you also change the CMakeLists.txt lines here.

#### Step 2 - Run the git submodule update

The erc-software-rover git repo utilizes a number of other submodules. Initialize them with this line before proceeding with any other step:

```bash
git submodule update --init --recursive
```

#### Step 3 - Run colcon build

Now you can properly build the ROS2 node 'aruco\_track':

```bash
colcon build --packages-select aruco_track
```

or simply:

```
colcon build
```

#### Step 4 - source the terminal

After you run colcon build, you want to source every time before running the node:

```bash
./install/setup.bash
```

#### Step 5 - Run the ROS2 Node

```bash
ros2 run aruco_track aruco_tf2_node
```

#### Step 6 - Select Video Source

Self explanatory based on the terminal print message:

```bash
=== ArUco Tracker ===
Select video source:
  [ENTER]  Use default USB camera (index 0)
  [0/1/2]  Use a different camera index
  [URL]    Use an IP camera / stream URL

Examples:
  http://192.168.0.152:8080/video   (IP Webcam)
  0
```

Default value is index 0. This will use the camera connected on '/video0'. Other values will use different outputs, '/video1' or '/video2' and so on if you have multiple connected cameras.

IP Webcam means using a mobile application called "IP Webcam" that starts a local server to transmit your mobile phone's camera feed to your computer. The IP will frequently change when you start the server.

#### Step 5 - View Detected ArUco Poses

You can run the following command to see the detected ArUco IDs and pose estimations right within your terminal, however the aruco\_tf2\_node must be running in parallel in a separate terminal.

```bash
ros2 topic echo /tf
```

Alternatively, you can view the pose estimation with rviz.

You can expect a simillar output:

```bash
---
transforms:
- header:
    stamp:
      sec: 1767899812
      nanosec: 662838346
    frame_id: camera_frame
  child_frame_id: aruco_11
  transform:
    translation:
      x: -0.03687934682250437
      y: -0.012666235570217378
      z: 0.13659864132869448
    rotation:
      x: -0.6982514505098149
      y: 0.7123967508805725
      z: -0.0574988868747503
      w: 0.04036903768865494
---
```

### Camera Calibration

<p class="callout warning">The CharUco camera calibration cpp file somehow vanished during development. It must be reimplemented to obtain a better `charuco\_camera\_params.yml` file with more optimized parameters.</p>

TODO: write this section once camera calibration works again.

UPDATE: Was able to recover the file now just have to re-implement it.

# YOLO

<p class="callout warning">This section is under construction until we define more things for computer vision with YOLO.</p>

# Path Planning

# Overview

<p class="callout danger">Work on this subsystem has not started yet</p>

# Rover Communications

# Overview

Communication is a subsystem responsible for bridging the two distinct networks present on the rover: the Ethernet Network and the ROS2 Network. Without it, Jonny Boi (the Jetson Orin Nano) would have no way to talk to the hardware microcontrollers or the Basestation, and the internal ROS2 software stack would have no way to act on external inputs or send commands outward.

On the Ethernet side, the Jetson connects to the hardware boards (ArmBoard, DriveBoard, SensorBoard, ContainerBoard) and to the Basestation over WiFi. On the ROS2 side, the Communications node exchanges messages with internal nodes such as the Behavior Node, which handles business logic, and the Controller Node, which manages hardware commands.

<span style="white-space: pre-wrap;">All messages sent over the network, whether towards the Basestation or the hardware microcontrollers, are encoded as Protobuffers, wrapped inside a </span>[PBEnvelope](https://bookstack.roboteamtwente.nl/books/jonny-boi/page/pbenvelope "PBEnvelope"). The PBEnvelope acts as a typed container that lets the receiver identify what kind of message it is receiving before deserializing the payload. These wrapped messages are transmitted as raw bytes inside UDP packets.

The Communications node operates in both directions:

- Inbound (Ethernet -&gt; ROS2): UDP packets received from the Basestation or hardware boards are parsed, the protobuf payload is extracted from the PBEnvelope, and the data is converted into custom ROS2 messages before being published over the appropriate ROS2 topic for other nodes to consume.
- Outbound (ROS2 -&gt; Ethernet): The Communications node subscribes to relevant ROS2 topics, converts the incoming ROS2 messages back into Protobuffers, wraps them in a PBEnvelope, and transmits them as UDP packets to the intended destination, either the Basestation or a hardware board.

<span style="white-space: pre-wrap;">Consider the Rover Architecture diagram </span>[Rover Architecture diagram](https://bookstack.roboteamtwente.nl/books/jonny-boi/page/overview "Overview")<span style="white-space: pre-wrap;"> to get a visual overview of how the Communications node sits at the center of these data flows.</span>

<span style="white-space: pre-wrap;">Before diving into the codebase, it is strongly recommended to read the </span>[ROS2 documentation](https://docs.ros.org/en/humble/Releases.html)<span style="white-space: pre-wrap;">, particularly the entries regarding topics, messages and publishers/subscribers: </span>  
[https://docs.ros.org/en/humble/Concepts/Basic/About-Interfaces.html](https://docs.ros.org/en/humble/Concepts/Basic/About-Interfaces.html)

<span style="white-space: pre-wrap;">You will also need background knowledge in C++ and UDP networking. The following guide is a solid starting point: </span>  
[https://beej.us/guide/bgnet/html/split-wide/](https://beej.us/guide/bgnet/html/split-wide/)

<span style="white-space: pre-wrap;"> </span>

# UDP Forwarder and ROS2 Publisher

## `<span class="editor-theme-code">udp_forwarder_node.cpp</span>`

This is the main file running the communications logic on Jonny Boi's side. It is a ROS2 node that simultaneously acts as a UDP server and a ROS2 publisher/subscriber. Its primary responsibilities are:

- Receiving raw encoded PBEnvelope packets over UDP from the Basestation or hardware microcontrollers
- Deserializing the protobuf payload and converting it into a custom ROS2 message
- Publishing that ROS2 message over the appropriate topic for internal nodes to consume
- In the outbound direction, subscribing to ROS2 topics and converting messages back into protobufs to be sent as UDP packets

## The Handler Pattern

<span style="white-space: pre-wrap;">The most important architectural concept in this file is the </span>**registry-based handler dispatch system**<span style="white-space: pre-wrap;">. Rather than having a large switch statement that handles every possible message type, the node maintains an </span>`<span class="editor-theme-code">unordered_map</span>`<span style="white-space: pre-wrap;"> that maps each </span>`<span class="editor-theme-code">PBEnvelope::PayloadCase</span>`<span style="white-space: pre-wrap;"> enum value to a dedicated handler object.</span>

<span style="white-space: pre-wrap;">When a UDP packet arrives, the node parses it as a </span>`<span class="editor-theme-code">PBEnvelope</span>`<span style="white-space: pre-wrap;"> and calls </span>`<span class="editor-theme-code">payload_case()</span>`<span style="white-space: pre-wrap;"> to identify the message type. It then looks up the corresponding handler in the map and calls </span>`<span class="editor-theme-code">handle(envelope)</span>`<span style="white-space: pre-wrap;"> on it. Each handler is a class that extends the abstract </span>`<span class="editor-theme-code">Handler</span>`<span style="white-space: pre-wrap;"> base class and is solely responsible for one message type — deserializing the protobuf, mapping its fields to a ROS2 message, and publishing it on the correct topic.</span>

Registering a handler looks like this:

```cpp
handlers_.emplace(
    static_cast<int>(PBEnvelope::kImuInfo),
    std::make_unique<ImuHandler>(this, "imu_data", 10)
);
```

<span style="white-space: pre-wrap;">The three constructor arguments are the node pointer (so the handler can create a publisher), the ROS2 topic name to publish on, and the publisher queue size. Adding support for a new message type is as simple as writing a new handler class and adding one </span>`<span class="editor-theme-code">emplace</span>`<span style="white-space: pre-wrap;"> call here — the rest of the dispatch logic requires no changes. See the </span>**Adding a New Message Type**<span style="white-space: pre-wrap;"> page for a step-by-step guide.</span>

## The rx\_loop Background Thread

<span style="white-space: pre-wrap;">UDP receiving does not happen on the ROS2 spin thread. Instead, the node spawns a dedicated background thread that runs </span>`<span class="editor-theme-code">rx_loop()</span>`<span style="white-space: pre-wrap;"> continuously, blocking on </span>`<span class="editor-theme-code">recvfrom()</span>`<span style="white-space: pre-wrap;"> until a datagram arrives. This design ensures that the ROS2 executor is never blocked waiting for network I/O, and that incoming packets are processed as fast as the network delivers them regardless of what the ROS2 stack is doing.</span>

<span style="white-space: pre-wrap;">This is worth keeping in mind when debugging. If you see unexpected behavior related to timing or message ordering, the source may be a race condition between the rx\_loop thread and the ROS2 spin thread. The </span>`<span class="editor-theme-code">running_</span>`<span style="white-space: pre-wrap;"> flag is declared as </span>`<span class="editor-theme-code">std::atomic<bool></span>`<span style="white-space: pre-wrap;"> specifically to ensure safe cross-thread signaling during shutdown.</span>

## UDP Forwarding Behavior

<span style="white-space: pre-wrap;">Before dispatching to a handler, the node forwards every raw UDP datagram to both </span>`<span class="editor-theme-code">dstA_</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">dstB_</span>`<span style="white-space: pre-wrap;"> unconditionally. This means every message type, regardless of its intended destination, is forwarded to both addresses. This is a known architectural limitation — see the </span>**Known Limitations**<span style="white-space: pre-wrap;"> page for more context and the rationale behind it.</span>

## Configuration Parameters

The node's network configuration is fully driven by ROS2 parameters declared at startup:

<table class="align-center" id="bkmrk-parameterdefaultdesc"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th class="align-center">Parameter

</th><th>Default

</th><th>Description

</th></tr><tr><td class="align-center">`<span class="editor-theme-code">listen_port</span>`

</td><td>5000

</td><td>UDP port the node listens on

</td></tr><tr><td>`<span class="editor-theme-code">dst_a_port</span>`

</td><td>6000

</td><td>First forwarding destination port

</td></tr><tr><td>`<span class="editor-theme-code">dst_b_port</span>`

</td><td>6001

</td><td>Second forwarding destination port

</td></tr><tr><td>`<span class="editor-theme-code">dst_ip</span>`

</td><td>127.0.0.1

</td><td>Destination IP for both forwarding targets

</td></tr></tbody></table>

<span style="white-space: pre-wrap;">These can be overridden at launch time via a ROS2 launch file or </span>`<span class="editor-theme-code">--ros-args</span>`<span style="white-space: pre-wrap;"> flags without recompiling, making it easy to reconfigure the node for different network environments such as the Jetson on the rover versus a local development machine.UDP Forwarding</span>

<span style="white-space: pre-wrap;">Currently, every received UDP packet is forwarded raw to both </span>`<span class="editor-theme-code">dstA_</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">dstB_</span>`<span style="white-space: pre-wrap;"> before handler dispatch.</span>

<p class="callout warning">This implementation will CHANGE SOON. to forward ONLY to appropriate destinations.</p>

# Protobuffers

## What are Protobuffers?

<span style="white-space: pre-wrap;">Protocol Buffers (Protobuffers or protobufs) are Google's binary serialization format for structured data. Compared to alternatives like JSON or XML, they are significantly more compact and faster to serialize/deserialize, making them well-suited for real-time communication between the rover's subsystems. They are also strongly typed and language-agnostic, the same </span>`<span class="editor-theme-code">.proto</span>`<span style="white-space: pre-wrap;"> definition can generate code for C++, Python, Rust, and others. For a general introduction, refer to the official documentation: </span>  
[https://protobuf.dev/overview/](https://protobuf.dev/overview/)

## The ERC-Protobufs Repository

<span style="white-space: pre-wrap;">All protobuf definitions used by the rover live in a shared repository called </span>**ERC-Protobufs**<span style="white-space: pre-wrap;">, which is used as a dependency across the rover's software stack. You can find it linked from the </span>[Starting with Protobufs](https://bookstack.roboteamtwente.nl/books/communication-system/page/starting-with-protobufs "Starting with Protobufs")<span style="white-space: pre-wrap;"> page.</span>

<span style="white-space: pre-wrap;">The repository is organized under a </span>`<span class="editor-theme-code">components/</span>`<span style="white-space: pre-wrap;"> folder, where each subfolder groups messages by the hardware board or subsystem they belong to:</span>

```
components/
  arm_board/
  driving_board/
  sensor_board/
  container_board/
  basestation/
  common/
```

<span style="white-space: pre-wrap;">For example, </span>`<span class="editor-theme-code">components/basestation/detected_object.proto</span>`<span style="white-space: pre-wrap;"> contains the message definition for a single YOLO-detected object sent from Jonny Boi to the Basestation. Placing it under </span>`<span class="editor-theme-code">basestation/</span>`<span style="white-space: pre-wrap;"> makes its intended direction and purpose immediately clear.</span>

## Naming Conventions

<span style="white-space: pre-wrap;">Every message name in the repository must be </span>**globally unique**, regardless of which folder it lives in. This is because proto3 imports reference files by path, but message names exist in a flat global namespace, two messages with the same name will cause build failures.

The convention used across the rover is to prefix every message name with its component:

- `<span class="editor-theme-code">ArmBoardControlSignals</span>`
- `<span class="editor-theme-code">BasestationControlMode</span>`
- `<span class="editor-theme-code">SensorBoardIMUInfo</span>`
- `<span class="editor-theme-code">DrivingBoardMotorMessage</span>`

Follow this convention strictly when adding new messages.

## How Protobufs are Compiled

<span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">comms</span>`<span style="white-space: pre-wrap;"> package does not manually clone ERC-Protobufs. Instead, CMake fetches it automatically at build time using </span>`<span class="editor-theme-code">FetchContent</span>`<span style="white-space: pre-wrap;">, pinned to a specific commit hash in </span>`<span class="editor-theme-code">CMakeLists.txt</span>`:

```cmake
FetchContent_Declare(
  erc_protobufs
  GIT_REPOSITORY https://github.com/RoboTeamTwente/ERC-Protobufs.git
  GIT_TAG <commit_hash>
)
```

<span style="white-space: pre-wrap;">Once fetched, </span>`<span class="editor-theme-code">protoc</span>`<span style="white-space: pre-wrap;"> generates </span>`<span class="editor-theme-code">.pb.h</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">.pb.cc</span>`<span style="white-space: pre-wrap;"> files into the build directory, which are then compiled into the </span>`<span class="editor-theme-code">comms</span>`<span style="white-space: pre-wrap;"> node. This means that whenever new proto files are added to ERC-Protobufs and committed, the </span>`<span class="editor-theme-code">GIT_TAG</span>`<span style="white-space: pre-wrap;"> in </span>`<span class="editor-theme-code">CMakeLists.txt</span>`<span style="white-space: pre-wrap;"> must be updated to the new commit hash before the changes will be picked up by the build. The full procedure is covered in the </span>**Adding a New Message**<span style="white-space: pre-wrap;"> page.</span>

## The PBEnvelope

<span style="white-space: pre-wrap;">All protobuf messages on this rover are wrapped inside a </span>`<span class="editor-theme-code">PBEnvelope</span>`<span style="white-space: pre-wrap;"> before being transmitted over UDP. The PBEnvelope acts as a typed container that allows the receiver to identify which type of message is inside before deserializing the payload. See the dedicated </span>[PBEnvelope](https://bookstack.roboteamtwente.nl/books/jonny-boi/page/pbenvelope "PBEnvelope")<span style="white-space: pre-wrap;"> page for a full explanation.</span>

## <span style="white-space: pre-wrap;"></span>

# PBEnvelope

## What is the PBEnvelope?

<span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">PBEnvelope</span>`<span style="white-space: pre-wrap;"> is a wrapper message defined in </span>`<span class="editor-theme-code">components/common/envelope.proto</span>`<span style="white-space: pre-wrap;"> in the ERC-Protobufs repository. Every protobuf message sent over UDP on this rover — whether from the Basestation, the Jetson, or a hardware microcontroller — is wrapped inside a </span>`<span class="editor-theme-code">PBEnvelope</span>`<span style="white-space: pre-wrap;"> before transmission.</span>

<span style="white-space: pre-wrap;">When a raw UDP datagram arrives, the receiver has no way of knowing what type of message is inside without some kind of type identifier. The PBEnvelope solves this by using proto3's </span>`<span class="editor-theme-code">oneof</span>`<span style="white-space: pre-wrap;"> field, which encodes the message type directly into the serialized bytes. The receiver calls </span>`<span class="editor-theme-code">payload_case()</span>`<span style="white-space: pre-wrap;"> on the deserialized envelope to determine the message type before extracting and deserializing the actual payload.</span>

## Structure

<span style="white-space: pre-wrap;">The PBEnvelope uses a </span>`<span class="editor-theme-code">oneof payload</span>`<span style="white-space: pre-wrap;"> block where each field corresponds to one registered message type. Only one field can be set at a time — that is the nature of </span>`<span class="editor-theme-code">oneof</span>`<span style="white-space: pre-wrap;">. When serialized, proto3 encodes which field is set, allowing the receiver to call </span>`<span class="editor-theme-code">envelope.payload_case()</span>`<span style="white-space: pre-wrap;"> and get back an enum value like </span>`<span class="editor-theme-code">PBEnvelope::kImuInfo</span>`<span style="white-space: pre-wrap;"> or </span>`<span class="editor-theme-code">PBEnvelope::kControlMode</span>`.

## Registered Message Types

Below is the full table of currently registered payload cases, their field numbers, and their communication direction:

<table class="align-center" id="bkmrk-field-numbermessage-"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>Field Number

</th><th>Message Type

</th><th>Direction

</th></tr><tr><td>1

</td><td>SensorBoardPHInfo

</td><td>Hardware → ROS2

</td></tr><tr><td>2

</td><td>ArmBoardControlSignals

</td><td>ROS2 → Hardware

</td></tr><tr><td>3

</td><td>ArmBoardDiagnostics

</td><td>Hardware → ROS2

</td></tr><tr><td>4

</td><td>ArmBoardMovementFeedback

</td><td>Hardware → ROS2

</td></tr><tr><td>5

</td><td>ArmBoardActualPositions

</td><td>Hardware → ROS2

</td></tr><tr><td>6

</td><td>ArmBoardTargetMovement

</td><td>ROS2 → Hardware

</td></tr><tr><td>7

</td><td>ArmBoardObstructions

</td><td>Hardware → ROS2

</td></tr><tr><td>8

</td><td>DrivingBoardDiagnostics

</td><td>Hardware → ROS2

</td></tr><tr><td>9

</td><td>DrivingBoardMotorMessage

</td><td>ROS2 → Hardware

</td></tr><tr><td>10

</td><td>DrivingBoardMotorPeriodicProgress

</td><td>Hardware → ROS2

</td></tr><tr><td>11

</td><td>SensorBoardDiagnostics

</td><td>Hardware → ROS2

</td></tr><tr><td>12

</td><td>SensorBoardGPSInfo

</td><td>Hardware → ROS2

</td></tr><tr><td>13

</td><td>SensorBoardIMUInfo

</td><td>Hardware → ROS2

</td></tr><tr><td>14

</td><td>SensorBoardLoadCellInfo

</td><td>Hardware → ROS2

</td></tr><tr><td>15

</td><td>SensorBoardPressureInfo

</td><td>Hardware → ROS2

</td></tr><tr><td>16

</td><td>BasestationControlMode

</td><td>BS → ROS2

</td></tr><tr><td>17

</td><td>BasestationDetectedObject

</td><td>ROS2 → BS

</td></tr><tr><td>18

</td><td>BasestationObjectSelection

</td><td>BS → ROS2

</td></tr><tr><td>19

</td><td>BasestationRockMeasureRequest

</td><td>BS → ROS2

</td></tr><tr><td>20

</td><td>BasestationRockMeasureResult

</td><td class="align-center">ROS2 → BS

</td></tr><tr><td>21

</td><td>BasestationRoverLocalization

</td><td class="align-center">ROS2 → BS

</td></tr></tbody></table>

**Important:**<span style="white-space: pre-wrap;"> Field numbers in a </span>`<span class="editor-theme-code">oneof</span>`<span style="white-space: pre-wrap;"> block are permanent. Once a field number is assigned to a message type, it must never be reused for a different type — even if the original message is removed. This would break deserialization for any system still using an older version of the envelope. Always use the next available field number when adding a new entry.</span>

## <span style="white-space: pre-wrap;">A note on </span>`<span class="editor-theme-code">BasestationDetectedObject</span>`

<span style="white-space: pre-wrap;">Unlike most messages which carry a complete snapshot of state, </span>`<span class="editor-theme-code">BasestationDetectedObject</span>`<span style="white-space: pre-wrap;"> sends </span>**one detected object per message**<span style="white-space: pre-wrap;">. This is because Embedded requires fixed-size protobuf messages and cannot handle </span>`<span class="editor-theme-code">repeated</span>`<span style="white-space: pre-wrap;"> fields (which are dynamically sized). The Basestation reconstructs the full frame by collecting all messages sharing the same </span>`<span class="editor-theme-code">frame_id</span>`<span style="white-space: pre-wrap;"> until it has received </span>`<span class="editor-theme-code">total_count</span>`<span style="white-space: pre-wrap;"> of them. See </span>`<span class="editor-theme-code">components/basestation/detected_object.proto</span>`<span style="white-space: pre-wrap;"> for the full definition.</span>

## <span style="white-space: pre-wrap;">A note on </span>`<span class="editor-theme-code">message_types.proto</span>`

<p class="callout warning">TLDR: I don't remember what we were doing with this file.</p>

<span style="white-space: pre-wrap;">The ERC-Protobufs repo contains a file called </span>`<span class="editor-theme-code">message_types.proto</span>`<span style="white-space: pre-wrap;"> which defines a </span>`<span class="editor-theme-code">PBMessageType</span>`<span style="white-space: pre-wrap;"> enum. This file is a leftover from an earlier architecture where a manual type ID system was planned. It is explicitly excluded from the build in </span>`<span class="editor-theme-code">CMakeLists.txt</span>`<span style="white-space: pre-wrap;"> because its enum values collide with message names in the global proto3 namespace. It is unused, do not reference it or add new entries to it.</span>

## Trailing Zero Bytes

<span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">envelope.proto</span>`<span style="white-space: pre-wrap;"> file includes an important note: all PBEnvelopes sent over the network must have trailing zero bytes removed before transmission. Proto3 serializes unset fields as zero bytes, and stripping them reduces network traffic. The receiver must pad the received datagram back to the full PBEnvelope size before deserializing. This is handled by the protobuf library's </span>`<span class="editor-theme-code">SerializeToString</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">ParseFromArray</span>`<span style="white-space: pre-wrap;"> functions when used correctly.</span>

## Adding a New Message Type

<span style="white-space: pre-wrap;">To add a new message type to the PBEnvelope, see the dedicated </span>[Adding a new message](https://bookstack.roboteamtwente.nl/books/jonny-boi/page/adding-a-new-message "Adding a new message")<span style="white-space: pre-wrap;"> page for the full step-by-step procedure.</span>

# Integration with ROS2

## Overview

The Communications node bridges the gap between raw UDP/protobuf packets and the ROS2 ecosystem running on Jonny Boi. This page explains how incoming protobuf messages get converted into ROS2 messages and published on topics, the handler pattern that makes this extensible, and how custom ROS2 message types are defined.

## Custom ROS2 Message Types

ROS2 uses its own message format (`<span class="editor-theme-code">.msg</span>`<span style="white-space: pre-wrap;"> files) to define the structure of data exchanged between nodes over topics. These are separate from protobuf definitions, they exist so that internal ROS2 nodes like the Behavior Node can work with typed, idiomatic ROS2 messages rather than raw protobuf objects.</span>

<span style="white-space: pre-wrap;">All custom message definitions live in the </span>`<span class="editor-theme-code">msg/</span>`<span style="white-space: pre-wrap;"> folder of the </span>`<span class="editor-theme-code">comms</span>`<span style="white-space: pre-wrap;"> package:</span>

```
comms/msg/
  ImuSensorInformation.msg
  SensorBoardGPSInfo.msg
  SensorBoardPHInfo.msg
  SensorBoardDiagnostics.msg
  SensorState.msg
```

<span style="white-space: pre-wrap;">These are registered in </span>`<span class="editor-theme-code">CMakeLists.txt</span>`<span style="white-space: pre-wrap;"> via </span>`<span class="editor-theme-code">rosidl_generate_interfaces</span>`<span style="white-space: pre-wrap;">, which generates the corresponding C++ types at build time. The generated types follow the naming convention </span>`<span class="editor-theme-code">comms::msg::MessageName</span>`<span style="white-space: pre-wrap;"> and can be included in any C++ file as </span>`<span class="editor-theme-code">#include "comms/msg/message_name.hpp"</span>`<span style="white-space: pre-wrap;">. For more on ROS2 interfaces, refer to the official documentation: </span>[https://docs.ros.org/en/humble/Concepts/Basic/About-Interfaces.html](https://docs.ros.org/en/humble/Concepts/Basic/About-Interfaces.html)

## The Handler Pattern

<span style="white-space: pre-wrap;">The handler pattern is the core architectural decision that makes adding new message types straightforward without ever touching the dispatch logic in </span>`<span class="editor-theme-code">udp_forwarder_node.cpp</span>`.

<span style="white-space: pre-wrap;">Every message type that needs to be converted and published as a ROS2 topic is represented by a handler class. All handlers extend the abstract </span>`<span class="editor-theme-code">Handler</span>`<span style="white-space: pre-wrap;"> base class defined in </span>`<span class="editor-theme-code">include/comms/udp/handler.hpp</span>`:

```cpp
class Handler {
public:
    virtual ~Handler() = default;
    virtual void handle(const PBEnvelope& envelope) = 0;
};
```

<span style="white-space: pre-wrap;">Each concrete handler class — one per message type — lives in </span>`<span class="editor-theme-code">include/comms/udp/handlers/</span>`<span style="white-space: pre-wrap;"> and its corresponding </span>`<span class="editor-theme-code">.cpp</span>`<span style="white-space: pre-wrap;"> in </span>`<span class="editor-theme-code">src/handlers/</span>`. Its job is exactly three things:

1. <span style="white-space: pre-wrap;">Extract the specific protobuf message from the </span>`<span class="editor-theme-code">PBEnvelope</span>`
2. Map the protobuf fields to the equivalent ROS2 message fields
3. Publish the ROS2 message on the appropriate topic

<span style="white-space: pre-wrap;">As an example, </span>`<span class="editor-theme-code">ImuHandler</span>`<span style="white-space: pre-wrap;"> extracts </span>`<span class="editor-theme-code">SensorBoardIMUInfo</span>`<span style="white-space: pre-wrap;"> from the envelope, maps its accelerometer, gyroscope and magnetometer fields to a </span>`<span class="editor-theme-code">comms::msg::ImuSensorInformation</span>`<span style="white-space: pre-wrap;"> message, and publishes it on the </span>`<span class="editor-theme-code">imu_data</span>`<span style="white-space: pre-wrap;"> topic.</span>

## Handler Registration

<span style="white-space: pre-wrap;">Handlers are registered in the constructor of </span>`<span class="editor-theme-code">udp_forwarder_node.cpp</span>`<span style="white-space: pre-wrap;"> using a simple </span>`<span class="editor-theme-code">unordered_map</span>`<span style="white-space: pre-wrap;"> keyed by </span>`<span class="editor-theme-code">PBEnvelope::PayloadCase</span>`:

```cpp
handlers_.emplace(
    static_cast<int>(PBEnvelope::kImuInfo),
    std::make_unique<ImuHandler>(this, "imu_data", 10)
);
```

<span style="white-space: pre-wrap;">The three constructor arguments are the node pointer (so the handler can create a ROS2 publisher), the topic name to publish on, and the publisher queue size. To add support for a new message type, you write a new handler class and add one line here. The dispatch logic in </span>`<span class="editor-theme-code">rx_loop</span>`<span style="white-space: pre-wrap;"> requires no changes at all.</span>

## The Full Inbound Flow

The complete path from a raw UDP packet to a ROS2 topic looks like this:

[![image.png](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/scaled-1680-/Mjfimage.png)](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/Mjfimage.png)

<span style="white-space: pre-wrap;">If no handler is registered for a given </span>`<span class="editor-theme-code">payload_case</span>`, the node logs a throttled warning and drops the packet. This means unhandled message types fail silently, keep this in mind when debugging missing data.

## The Outbound Flow

The outbound direction works in reverse, the Communications node subscribes to a ROS2 topic, converts the incoming message back into a protobuf, wraps it in a PBEnvelope, and sends it as a UDP packet to the appropriate destination. This path is not yet fully implemented for all message types and will be extended as the Behavior Node develops.

## Adding a New Message Type

<span style="white-space: pre-wrap;">For a step-by-step guide on adding a new message type end-to-end, from the proto definition all the way to a registered handler — see the dedicated </span>[Adding a new message](https://bookstack.roboteamtwente.nl/books/jonny-boi/page/adding-a-new-message "Adding a new message")<span style="white-space: pre-wrap;"> page.</span>

# Adding a new message



# Testing with udp_client



# Dev Environment Setup



# Known Limitations

# SLAM

# Overview

<p class="callout danger">This subsystem is still in experimentation phase and WILL change.</p>

<span style="white-space: pre-wrap;">The autonomous navigation of the rover is supported by the use of an </span>**Intel RealSense D435i**<span style="white-space: pre-wrap;"> depth camera. This sensor provides synchronized RGB, depth, and inertial measurements (IMU), which are essential for perception and mapping tasks.</span>

<span style="white-space: pre-wrap;">The software interface for the camera is provided through the </span>**realsense-ros ROS2 wrapper**<span style="white-space: pre-wrap;"> package </span>[realsenseai/realsense-ros: ROS Wrapper for RealSense™ Cameras](https://github.com/realsenseai/realsense-ros)<span style="white-space: pre-wrap;">, which enables integration of the camera with the ROS 2 ecosystem. This repository has been cloned into the workspace and is included as a package. Additionally, the </span>**librealsense SDK** [realsenseai/librealsense: RealSense SDK](https://github.com/realsenseai/librealsense), which provides the low-level drivers and APIs for the camera—is also included as a separate package.

<span style="white-space: pre-wrap;">The primary package responsible for SLAM (Simultaneous Localization and Mapping) and map generation is </span>`<span class="editor-theme-code">rtt_slam</span>`<span style="white-space: pre-wrap;">. Within this package, all required dependencies are declared in the </span>`<span class="editor-theme-code">package.xml</span>`<span style="white-space: pre-wrap;"> file, ensuring proper integration with the rest of the ROS 2 system.</span>

The process takes the RGB and depth frames along with the IMU data and produces a voxel-based local costmap as the output, using Nav2's built-in Voxel Layer plugin. The Nav2 framework is utilized to produce the map and for autonomous navigation.

To launch SLAM:

```bash
ros2 launch rtt_slam slam_launch.yaml
```

The launch file starts:

1. Realsense camera drivers
2. RTAB-Map odometry and SLAM
3. IMU filtering
4. Navigation2 (Nav2)
5. Costmap Visualization utilities

### Launch File Documentation

The launch files define and orchestrate all nodes and processes required for perception, SLAM, and data streaming. They ensure proper initialization order and parameter configuration. All four launch files are written in the ROS 2 YAML launch format (`<span class="editor-theme-code">.yaml</span>`).

#### 1. camera\_launch.yaml

Responsible for starting the depth camera.

The launch file:

- <span style="white-space: pre-wrap;">Loads RealSense configuration from </span>`<span class="editor-theme-code">config/camera_realsense.yaml</span>`
- Starts the RealSense camera driver

Configuration:

- Enables the gyroscope, accelerometer, IMU synchronization, and point cloud generation.
- Aligns depth images to the RGB camera.
- Reduces image resolution for improved stability and performance.

#### 2. rtabmap\_launch.yaml

Responsible for localization and map generation.

The launch file:

- <span style="white-space: pre-wrap;">Loads RTAB-Map configuration from </span>`<span class="editor-theme-code">config/rtabmap.yaml</span>`
- Remaps camera RGB, depth, and camera info topics
- Starts RGB-D odometry
- Starts RTAB-Map SLAM
- Starts the Madgwick IMU filter
- <span style="white-space: pre-wrap;">Publishes a static transform between </span>`<span class="editor-theme-code">base_link</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">camera_link</span>`

Configuration:

- Uses synchronized RGB and depth images from the RealSense camera.
- Uses filtered IMU orientation estimates for odometry.
- Deletes any existing RTAB-Map database on startup.
- Optionally launches the RTAB-Map visualization tool.

#### 3. navigation\_launch.yaml

Responsible for autonomous navigation and costmap generation.

The launch file:

- <span style="white-space: pre-wrap;">Loads navigation parameters from </span>`<span class="editor-theme-code">config/navigation.yaml</span>`
- Starts the Nav2 navigation stack
- Starts costmap visualization nodes

Configuration:

- Uses the RTAB-Map occupancy grid as the global map.
- Uses a voxel-based local costmap generated from RealSense point clouds.
- Enables obstacle marking and clearing.
- Uses the DWB local planner for path following.

#### 4. slam\_launch.yaml

Responsible for launching the complete SLAM system.

The launch file:

- <span style="white-space: pre-wrap;">Launches </span>`<span class="editor-theme-code">camera_launch.yaml</span>`
- <span style="white-space: pre-wrap;">Launches </span>`<span class="editor-theme-code">rtabmap_launch.yaml</span>`
- <span style="white-space: pre-wrap;">Launches </span>`<span class="editor-theme-code">navigation_launch.yaml</span>`

Configuration:

- Combines perception, localization, mapping, and navigation into a single launch command.
- Ensures all required subsystems are started together.

####   


#### Video Streaming to Basestation

```
ExecuteProcess(
    cmd=['/bin/bash', os.path.expanduser('~/stream.sh')],
)
```

A separate script (`<span class="editor-theme-code">stream.sh</span>`) is executed to stream camera data to the base station.

### Streaming Script Documentation

<span style="white-space: pre-wrap;">This script uses </span>**GStreamer**<span style="white-space: pre-wrap;"> to transmit video over UDP.</span>

**Paragraph Breakdown:**

```
rosimagesrc ros-topic="/camera/camera/color/image_raw"
```

Captures image directly from a ROS topic.

```
videoconvert ! video/x-raw,format=I420
```

Converts image format for encoder compatibility

```
x264enc tune=zerolatency speed-preset=superfast
```

Encodes video using H.264 with low-latency settings

```
rtph264pay pt=96
```

Packages encoded video into RTP packets

```
udpsink host=145.126.xx.XXX port=4500
```

Sends the stream to the base station via UDP

### RViz2

RViz2 is used as the primary visualization interface for the rover system. It allows real-time monitoring of sensor data such as RGB images, depth maps, point clouds, and TF frames. Additionally, it provides tools to visualize occupancy grids and SLAM outputs, aiding in debugging and system validation. It is used to test the implementation of the mapping and the working of the camera.