Developer Guide - Forestry-Robotics-UC/fruc_dataset_apparatus GitHub Wiki
This guide is for developers who need to:
- Modify or extend the FRUC Dataset Apparatus system
- Add new sensors or recording capabilities
- Debug and maintain the codebase
- Contribute improvements to the project
- Familiarity with ROS2 concepts (nodes, topics, services, launch files)
- Basic Docker and containerization knowledge
- Linux command-line proficiency
- C++ or Python programming skills
- Docker and Docker Compose
- Git and version control basics
- A text editor or IDE (VSCode recommended)
- ROS2 Jazzy development environment (optional, but useful)
fruc_dataset_apparatus/
├── .github/ # GitHub workflows and CI/CD
├── .git/ # Git repository data
├── .gitignore # Git ignore rules
├── .names_01, .names_02 # Random dataset naming files
├── README.md # Main readme
│
├── docker/ # Docker configurations
│ ├── Dockerfile.* # Individual container definitions
│ ├── docker-compose.yml # Orchestration configuration
│ ├── docker-compose.link-local.yml # Network variant
│ ├── pre_recording_test.sh # Automated testing script
│ │
│ ├── docker_build/ # Build-time resources
│ │ ├── entrypoint.sh # Container startup script
│ │ ├── apparatus_description/ # Robot URDF package
│ │ │ ├── launch/ # ROS2 launch files
│ │ │ ├── urdf/ # Robot model files
│ │ │ ├── config/ # Configuration files
│ │ │ └── meshes/ # 3D mesh files (STL, OBJ)
│ │ └── rslaunch/ # RealSense custom launch files
│ │
│ └── docker_shared/ # Runtime shared volumes
│ ├── scripts/ # Sensor launch scripts
│ │ ├── xsens-launch.sh
│ │ ├── realsense-launch.sh
│ │ ├── emlid-launch.sh
│ │ ├── ouster.launch.py
│ │ ├── mtnode.py
│ │ └── mtdevice.py
│ └── ouster_config.yaml # Ouster settings
│
├── realsense-udev-rules/ # Device permission rules
│ ├── realsense-udev-install.sh
│ └── README.md
│
├── rviz/ # RViz visualization configs
│ ├── 435i-udrf.rviz
│ ├── realsense.rviz
│ └── *.rviz files
│
├── rosbags/ # Recording output directory
│ └── [timestamp]__[name]/ # Individual recordings
│
├── launch-system.sh # Main launch script
├── gnome-launch-system.sh # GNOME variant
├── stop-system.sh # System shutdown script
├── gnome-stop-system.sh # GNOME variant
└── tty-perms.sh # Serial port permissions
fruc_dataset_apparatus.wiki/
├── Home.md # Wiki home page
├── Technical-Architecture.md (NEW)
├── Tools-Libraries-Dependencies.md (NEW)
├── Developer-Guide.md (THIS FILE - NEW)
├── Installation-Setup.md (NEW)
├── User-Manual.md (NEW)
├── Scripts-Reference.md (NEW)
└── [existing wiki files]
Create a new Dockerfile in docker/Dockerfile.newimenor:
# NEW SENSOR
FROM ros:jazzy-ros-base
# Install base dependencies
RUN apt update && apt install -y --no-install-recommends \
build-essential \
git \
python3-colcon-common-extensions \
python3-pip \
python3-rosdep
# Create workspace
WORKDIR /docker_ws/src/
# Clone the sensor driver repository
RUN git clone https://github.com/example/example-ros-driver.git
# Install ROS dependencies
RUN rosdep update && rosdep install --from-paths . -y --ignore-src
# Build workspace
WORKDIR /docker_ws
RUN . /opt/ros/jazzy/setup.sh && colcon build --packages-up-to example_driver
# Copy entrypoint and make executable
COPY ./docker_build/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Set container defaults
ENTRYPOINT [ "/entrypoint.sh" ]
CMD [ "ros2", "launch", "example_driver", "example_driver.launch.xml" ]Add your service to docker/docker-compose.yml:
newsensor:
build:
context: .
dockerfile: Dockerfile.newimenor
image: ros2-apparatus-newimenor:jazzy
stdin_open: true
tty: true
privileged: true
container_name: ros2-apparatus-newimenor
networks:
- ros2-net
devices:
- /dev/ttyUSB1:/dev/ttyUSB1 # If using serial
volumes:
- ./docker_shared:/shared
command:
- ros2
- launch
- example_driver
- example_driver.launch.xml
- frame_id:=newimenor_sensorModify launch-system.sh to include your sensor's topics:
# In the topics checklist section, add:
/newsensor/data "New Sensor Data" on \
/newsensor/status "New Sensor Status" off \If your sensor requires special dependencies, update Dockerfile.recording:
RUN git clone https://github.com/example/example-driver.gitCreate launch files in Python format (.launch.py):
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
DeclareLaunchArgument(
name='frame_id',
default_value='sensor_frame',
description='TF frame ID for the sensor'
),
DeclareLaunchArgument(
name='use_sim_time',
default_value='False',
description='Use simulation time'
),
Node(
package='sensor_driver_pkg',
executable='sensor_driver_node',
name='sensor_driver',
parameters=[{
'frame_id': LaunchConfiguration('frame_id'),
'use_sim_time': LaunchConfiguration('use_sim_time'),
}],
remappings=[],
),
])- Always use LaunchConfiguration for parametrization
- Declare all arguments at the top with defaults
- Use meaningful names for frame IDs and namespaces
- Document parameters in description fields
- Handle remappings for topic namespace consistency
The main orchestration script follows this pattern:
#!/bin/bash
# 1. SELECT RECORDING NAME
# - Generates default name from timestamp + random adjectives
# - Uses kdialog for user input
# 2. SELECT TOPICS FOR RECORDING
# - Presents kdialog checklist
# - Each line: /topic_name "Display Name" on/off
# 3. SELECT BAG SPLIT STRATEGY
# - Dialog to choose between size-based or time-based splitting
# 4. SELECT COMPRESSION PROFILE
# - Options: none, fastwrite, zstd_fast, zstd_small
# 5. CONFIRMATION
# - Displays all selected options
# - User confirms to proceed
# 6. LAUNCH SYSTEM
# - Starts Docker containers
# - Begins recordingFor Desktop (Docker):
# Navigate to docker directory
cd docker/
# Build a specific container
docker build -f Dockerfile.newimenor -t ros2-apparatus-newimenor:jazzy .
# Or use docker-compose
docker-compose build newimenor
# Test the container
docker-compose up -d newimenor
docker exec ros2-apparatus-newimenor ros2 topic list
docker-compose downFor SteamDeck (Podman):
# Navigate to docker directory
cd docker/
# Build a specific container
podman build -f Dockerfile.newimenor -t ros2-apparatus-newimenor:jazzy .
# Or use podman-compose
podman-compose build newimenor
# Test the container
podman-compose up -d newimenor
podman exec ros2-apparatus-newimenor ros2 topic list
podman-compose downBoth Docker and Podman use the same command syntax. Simply replace docker with podman and docker-compose with podman-compose:
| Operation | Docker | Podman |
|---|---|---|
| Build image | docker build ... |
podman build ... |
| Compose commands | docker-compose ... |
podman-compose ... |
| Execute in container | docker exec ... |
podman exec ... |
| View logs | docker logs ... |
podman logs ... |
| Interactive shell | docker exec -it ... |
podman exec -it ... |
For Desktop (Docker):
# Interactive shell in running container
docker exec -it ros2-apparatus-newimenor bash
# View container logs
docker logs ros2-apparatus-newimenor
# Rebuild with no cache
docker-compose build --no-cache newimenor
# Verbose build output
docker-compose build newimenor --verboseFor SteamDeck (Podman):
# Interactive shell in running container
podman exec -it ros2-apparatus-newimenor bash
# View container logs
podman logs ros2-apparatus-newimenor
# Rebuild with no cache
podman-compose build --no-cache newimenor
# Verbose build output
podman-compose build newimenor --verboseThe recording system uses two approaches:
ros2 run hector_recorder record \
-b 5000000000 \ # Size limit in bytes
--topics /topic1 /topic2 \
-o /rosbags/recording_nameAdvantages:
- Advanced filtering
- Custom compression
- Integration with ROS tools
ros2 bag record \
--storage-preset-profile zstd_fast \
-d 60 \ # Duration in seconds
--topics /topic1 /topic2 \
-o /rosbags/recording_nameAdvantages:
- Standard ROS2 tool
- Well-tested
- Direct MCAP format support
Edit launch-system.sh to change defaults:
# Default bag limit
bag_limit_value=$(kdialog --inputbox "Enter max bag size (GB):" "5")
# Default compression (in docker-compose)
storage_profile="zstd_fast"Located in docker_build/apparatus_description/urdf/:
-
robot.urdforrobot.urdf.xacro- Main robot model -
sensors.urdf.xacro- Sensor mount definitions -
materials.urdf.xacro- Visual properties
Edit URDF to add new sensor frame:
<!-- New Sensor Joint -->
<joint name="base_to_newsensor" type="fixed">
<parent link="base_link"/>
<child link="newsensor_link"/>
<origin xyz="0.1 0 0.05" rpy="0 0 0"/>
</joint>
<link name="newsensor_link">
<visual>
<geometry>
<mesh filename="package://apparatus_description/meshes/newsensor.stl"/>
</geometry>
</visual>
</link># From docker_build/apparatus_description/
python3 setup.py build
# Test URDF validity
check_urdf file.urdf
# View in RViz
ros2 launch apparatus_description view_robot.launch.py# List all active topics
ros2 topic list
# Check topic rate and type
ros2 topic hz /topic_name
ros2 topic type /topic_name
# Echo topic data
ros2 topic echo /topic_name
# Monitor all activity from a container
docker exec ros2-apparatus-xsens ros2 topic list -v# List TF tree
ros2 run tf2_tools view_frames
# Monitor specific transform
ros2 run tf2_ros tf2_echo base_link camera_link
# Check for transform gaps
ros2 run ros2_control_demo_example_12 static_broadcaster --frame-id base_link# Monitor container resource usage
docker stats ros2-apparatus-realsense
# Check ROS2 middleware settings
echo $ROS_MIDDLEWARE_IDENTIFIER
# Profile topic message rates
ros2 run ros2_tracing tracetools_launch example_hooksThe project uses .github/workflows/ for CI/CD:
- Automated Docker builds
- Container testing
- Documentation generation
- Release automation
# Verify Dockerfiles
docker-compose config
# Test composition startup
docker-compose up -d
sleep 10
ros2 topic list
docker-compose down
# Verify shell scripts
bash -n launch-system.sh # Check syntax
shellcheck launch-system.sh # Lint check-
Create feature branch
git checkout -b feature/add-new-sensor
-
Make changes
- Update Dockerfiles, scripts, launch files
- Test locally
- Document in code
-
Commit with clear messages
git commit -m "feat: add newimenor sensor driver" -
Push and create pull request
git push origin feature/add-new-sensor
-
Bash scripts: Use
bash -nfor syntax checking - Python: Follow PEP 8, use type hints where possible
- ROS2 launch files: Use consistent parameter naming
- Comments: Explain "why", not just "what"
Create a ROS2 node in docker_build/ to handle specialized recording:
import rclpy
from rclpy.node import Node
from rosbag2_py import SequentialWriter
class CustomRecorder(Node):
def __init__(self):
super().__init__('custom_recorder')
self.writer = SequentialWriter()
# Subscribe to topics
self.create_subscription(
Imu, '/imu/data',
self.imu_callback, 10)
def imu_callback(self, msg):
# Custom processing before recording
self.writer.write('imu_data', msg, rclpy.clock.Clock().now())Add hooks in recording container for automatic conversion:
# In docker/docker_build/entrypoint.sh
# After recording completes:
# Convert MCAP to legacy rosbag format
rosbag2 convert -i input.mcap -o output
# Generate metadata
python3 analyze_rosbag.py input.mcap > metadata.json-
Use
.dockerignoreto exclude unnecessary files - Multi-stage builds to reduce image size
- Layer caching - put frequently changing items last
- Compression profiles: Trade-off speed vs. size
- Topic filtering: Record only needed data
- QoS settings: Adjust message reliability
For distributed sensors, consider:
- Static IP assignment (link-local networks)
- Separate network bridges for high-bandwidth sensors
- DDS participant isolation via
ROS_DOMAIN_ID
Problem: Dockerfile build fails
Solution:
docker-compose build --no-cache --verbose sensor_name
# Check for missing dependencies in ROS repositories
rosdep search package_nameProblem: rosdep can't find dependencies
Solution:
rosdep update # Update the database
rosdep keys --all # List all available keysProblem: Sensor not publishing data
Solution:
# Check device permissions
ls -la /dev/ttyUSB*
# Reinstall udev rules
sudo ./realsense-udev-rules/realsense-udev-install.sh
# Restart container with privileged mode
docker-compose down
docker-compose up -dProblem: ROS topics not visible between containers
Solution:
# Verify network connectivity
docker-compose exec sensor_container ping publisher
# Check ROS_DOMAIN_ID
docker-compose exec sensor_container env | grep ROS_
# View network configuration
docker network inspect docker_ros2-netLast Updated: 2026-03-30
Related Documentation: Technical Architecture, Tools and Libraries