Skip to content

Repository files navigation

Porter Robot

Autonomous luggage-carrying robot for airports — built by VirtusCo.

Porter Robot autonomously navigates terminals, carries passenger luggage, and avoids obstacles using LIDAR, ToF, ultrasonic, and microwave sensors. An on-device AI assistant (Virtue) answers passenger questions, provides directions, checks flight status, and calls for assistance — all running locally on a Raspberry Pi.

Built on ROS 2 Jazzy with a custom YDLIDAR driver, scan processing pipeline, ESP32 motor/sensor firmware (Zephyr RTOS), fine-tuned Gemma 3 270M GGUF model, and a lightweight conversation orchestrator with tool execution.

Status: Phases 1, 3, 4, and 4.5 complete — LIDAR subsystem, ESP32 firmware + bridge, system orchestration, AI assistant with conversation orchestrator. Hardware tested with YDLIDAR X4 Pro (S2PRO). 324 tests across all packages.


Quick Start (Docker)

Docker is the primary workflow. Works on any PC with Docker installed — no ROS 2 or SDK setup needed.

# Clone
git clone https://github.com/austin207/Porter-ROS.git
cd Porter-ROS/porter_robot
# Build (downloads ROS 2 Jazzy image + builds YDLidar SDK + compiles workspace)
docker compose -f docker/docker-compose.dev.yml build
# Start development container
docker compose -f docker/docker-compose.dev.yml up -d
docker exec -it porter_dev bash
# Inside container — ROS 2 is already sourced
colcon test --event-handlers console_direct+ # Run tests
ros2 launch ydlidar_driver ydlidar_launch.py # Launch driver (needs LIDAR)

Hardware Testing (with LIDAR plugged in)

# Start with device passthrough
docker compose -f docker/docker-compose.dev.yml --profile hardware up -d
docker exec -it porter_robot_hw bash
# Run driver
ros2 launch ydlidar_driver ydlidar_launch.py port:=/dev/ttyUSB0
# In another terminal
ros2 topic echo /scan # View scan data
ros2 topic hz /scan # Check frequency
ros2 topic echo /diagnostics # Health status

AI Assistant

# Launch simple assistant (inference only)
ros2 launch porter_ai_assistant assistant_launch.py
# Launch full orchestrator (tool execution + conversation memory, for GUI)
ros2 launch porter_ai_assistant assistant_launch.py use_orchestrator:=true
# Query via topic
ros2 topic pub /porter/ai_query std_msgs/String "data: 'Where is Gate B12?'" --once
# Read response
ros2 topic echo /porter/ai_response

RViz2 Visualization

# On a machine with display (X11)
xhost +local:docker
docker compose -f docker/docker-compose.dev.yml --profile viz up

System Architecture

Hardware

┌──────────────────────────────────────────────────────────────┐
│ Porter Robot Hardware │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Raspberry Pi 5 (Master) │ │
│ │ • ROS 2 Jazzy + Nav2 │ │
│ │ • YDLIDAR X4 Pro (serial, /dev/ttyUSB0) │ │
│ │ • Virtue AI (Gemma 3 270M GGUF, 351 MB RSS) │ │
│ │ • Touchscreen display │ │
│ │ • Docker deployment │ │
│ └──────┬──────────────────────┬───────────────────────┘ │
│ │ USB CDC Serial │ USB CDC Serial │
│ ┌──────▼──────────┐ ┌──────▼──────────┐ │
│ │ ESP32 #1 │ │ ESP32 #2 │ │
│ │ Motor Control │ │ Sensor Fusion │ │
│ │ • 2× BTS7960 │ │ • ToF VL53L0x │ │
│ │ • Diff drive │ │ • Ultrasonic │ │
│ │ • SMF states │ │ • Microwave │ │
│ │ • Zephyr RTOS │ │ • Kalman fuse │ │
│ └─────────────────┘ │ • Zephyr RTOS │ │
│ └─────────────────┘ │
└──────────────────────────────────────────────────────────────┘

Software Stack

┌──────────────────────────────────────────────────────────────┐
│ Virtue AI Orchestrator │ Tool exec + memory
├──────────────────────────────────────────────────────────────┤
│ Virtue AI Assistant │ Gemma 3 270M GGUF + LoRA
├──────────────────────────────────────────────────────────────┤
│ Porter Orchestrator │ 9-state FSM, health monitor
├──────────────────────────────────────────────────────────────┤
│ Porter LIDAR Processor │ Filtering, smoothing, ROI
├────────────┬────────────────┬────────────────────────────────┤
│ YDLIDAR │ ESP32 Motor │ ESP32 Sensor │ C++ drivers
│ Driver │ Bridge │ Bridge │
├────────────┴────────────────┴────────────────────────────────┤
│ Docker + Entrypoint │ Container management
├──────────────────────────────────────────────────────────────┤
│ ROS 2 Jazzy / Ubuntu 24.04 Noble │ OS + middleware
└──────────────────────────────────────────────────────────────┘

TF Tree

map ──► odom ──► base_link ──► laser_frame
│ │ │
│ │ └── (static: URDF)
│ └── (odometry source: EKF / wheel odom)
└── (SLAM / localisation)

Topic Flow

YDLIDAR ──serial──► ydlidar_driver ──/scan──► porter_lidar_processor ──/scan/processed──► Nav2
│
└──/diagnostics──► lidar_health_monitor ──/porter/health_status──► state_machine
│
/porter/state
ESP32 #1 ◄──serial──► esp32_motor_bridge ◄──/cmd_vel──► Nav2
│
└──► /motor_status
ESP32 #2 ──serial──► esp32_sensor_bridge ──/environment──► Nav2
│
└──► /diagnostics
GUI ──/porter/ai_query──► virtue_orchestrator ──/porter/ai_response──► GUI
│
└── InferenceEngine + ToolExecutor (14 tools)

Packages

ROS 2 Packages (src/)

PackageLanguageDescription
ydlidar_driverC++17Custom YDLIDAR ROS 2 Jazzy driver. Publishes /scan + /diagnostics. Model-agnostic via YAML config.
porter_lidar_processorPythonScan pipeline: range clamp → outlier reject → median → smoothing → ROI → downsample. Publishes /scan/processed.
porter_orchestratorPython9-state system FSM + health monitor. Boot sequence, auto-recovery, health evaluation.
porter_esp32_bridgeC++17Serial bridge to ESP32 motor controller + sensor fusion. /cmd_vel ↔ binary protocol.
porter_ai_assistantPythonOn-device AI assistant (Gemma 3 270M GGUF + LoRA). Conversation orchestrator with 14-tool execution.

ESP32 Firmware (esp32_firmware/)

DirectoryDescription
common/Shared protocol (CRC16-CCITT, binary parser/encoder, transport abstraction)
motor_controller/Zephyr RTOS: BTS7960 PWM, differential drive, SMF state machine, watchdog
sensor_fusion/Zephyr RTOS: ToF + Ultrasonic + Microwave, Kalman filter, cross-validation
tests/Ztest: CRC16, protocol parser, transport (native_sim)
udev/Stable device naming rules (/dev/esp32_motors, /dev/esp32_sensors)

Planned Packages

PackageDescriptionStatus
nav2_configNavigation2 parametersPhase 2
porter_robot_urdfURDF/Xacro robot modelPhase 2

AI Assistant (Virtue)

On-device LLM for smart airport passenger assistance. Runs entirely on RPi 4/5 — no cloud required.

PropertyValue
ModelGoogle Gemma 3 270M IT (Q4_K_M GGUF, 241 MB)
LoRA AdaptersConversational (7.3 MB) + Tool-use (7.3 MB) — swapped at runtime
Runtimellama-cpp-python on CPU
InferenceConversational: ~497 ms, Tool-use: ~250 ms
Memory351 MB RSS (model + runtime)
Training Data12K examples (7K conversational + 5K tool-use)
Tools14 (directions, flights, amenities, luggage, assistance, escort, maps, etc.)
AI PersonaVirtue (distinct from "Porter" robot product name)

Orchestrator Architecture

User Query ──► ConversationOrchestrator
│
├── InferenceEngine.classify_query()
│ → 'conversational' or 'tool_use'
│
├── InferenceEngine.query() with LoRA adapter
│ → model response (may contain <tool_call>)
│
├── parse_tool_call() → ToolExecutor.execute()
│ → tool result (flight status, directions, etc.)
│
├── Re-infer with tool result for final response
│
└── Session memory (sliding window, per-passenger)

Configuration

LIDAR Parameters (ydlidar_params.yaml)

ParameterDefaultDescription
port/dev/ttyUSB0Serial device path
baudrate128000Baud rate (128000 for X4 Pro)
frame_idlaser_frameTF frame for LaserScan header
frequency10.0Motor target frequency (Hz)
angle_min / angle_max-180.0 / 180.0Scan angle range (degrees)
min_range / max_range0.01 / 12.0Valid range (metres)
singleChanneltrueMust match model (see below)
health_expected_freq4.0Actual scan delivery rate for health (Hz)

Single-channel LIDARs (one-way comms, singleChannel: true): X4, X4 Pro, X2, X2L, S2, S4/S2PRO, S4B

Dual-channel LIDARs (two-way comms, singleChannel: false): G4, G4 Pro, G6, G7, F4 Pro, TG series

Swap LIDAR model = change YAML config only. No code changes needed.

Orchestrator Parameters (orchestrator_params.yaml)

ParameterDefaultDescription
boot_grace_sec8.0DDS discovery grace period
health_check_patience_sec10.0Tolerate non-OK health window
boot_timeout_sec30.0Max wait for driver health
warn_consecutive_limit20WARNs before escalating to ERROR

AI Assistant Parameters (assistant_params.yaml)

ParameterDefaultDescription
model_pathmodels/gguf/gemma-3-270m-it-Q4_K_M.ggufBase GGUF model
default_adapterconversationalDefault LoRA adapter
max_tokens256Max generation tokens
temperature1.0Sampling temperature
n_ctx768Context window
memory_size10Conversation turns per session
session_timeout_sec300.0Session expiry (seconds)

Docker Services

Development (docker-compose.dev.yml)

ServiceProfileDescription
porter_dev(default)Dev shell with live code mount
porter_robot--profile hardwareFull device access for LIDAR testing
porter_viz--profile vizRViz2 with X11 forwarding

Production (docker-compose.prod.yml)

ServiceProfileDescription
porter_robot(default)Multi-stage minimal image, restart: unless-stopped
porter_test--profile testCI test runner
# Production build & deploy
docker compose -f docker/docker-compose.prod.yml build
docker compose -f docker/docker-compose.prod.yml up -d
# CI test run
docker compose -f docker/docker-compose.prod.yml --profile test up

Native Build (Without Docker)

Requires Ubuntu 24.04 with ROS 2 Jazzy and YDLidar SDK installed.

# Prerequisites
sudo apt install ros-jazzy-desktop python3-colcon-common-extensions python3-numpy
# Install YDLidar SDK
git clone https://github.com/YDLIDAR/YDLidar-SDK.git
cd YDLidar-SDK && mkdir build &&cd build
cmake .. -DCMAKE_BUILD_TYPE=Release && make -j$(nproc)&& sudo make install
sudo ldconfig &&cd ../..
# Build workspacecd porter_robot
source /opt/ros/jazzy/setup.bash
colcon build --symlink-install --cmake-args -Wno-dev
source install/setup.bash
# Run LIDAR driver
ros2 launch ydlidar_driver ydlidar_launch.py
# Run AI assistant (with orchestrator)
ros2 launch porter_ai_assistant assistant_launch.py use_orchestrator:=true

Testing

# All ROS 2 tests
colcon test --event-handlers console_direct+
colcon test-result --verbose
# Single package
colcon test --packages-select ydlidar_driver
colcon test --packages-select porter_ai_assistant
# ESP32 firmware tests (requires Zephyr toolchain)cd esp32_firmware && twister -T tests/ -p native_sim
PackageTestsDescription
ydlidar_driver9GTest (health monitor, scan conversion, config) + linters
porter_lidar_processor246 filter test classes + linters
porter_orchestrator23State machine + health monitor + linters
porter_esp32_bridgeC++ bridge (compile-tested, runtime needs hardware)
porter_ai_assistant5520 inference/config + 35 orchestrator + linters
ESP32 firmware (Ztest)178CRC16, protocol parser, transport (native_sim)

Development

Environment

FieldValue
ROS 2 DistroJazzy Jalisco
ROS Domain ID11
DDS / RMWrmw_fastrtps_cpp
C++ StandardC++17
Python3.12 (system, managed by ROS 2)
ESP32 RTOSZephyr 4.0
AI Runtimellama-cpp-python 0.3.16

Branch Strategy

BranchPurpose
mainStable, tested, deployable
prototypeCurrent development
feat/*Feature branches

Commit Convention

<type>(<scope>): <short description>

Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore

Key Files

FilePurpose
CLAUDE.mdAI engineer instructions (18 sections, 39 lessons learned)
OBJECTIVES.mdProject goals, timeline, hardware architecture
COMPANY.mdVirtusCo context, team, product vision
CHANGES.md30-entry change log with before/after code
DevLogs/Session-by-session development logs

Project Roadmap

PhaseDescriptionStatus
1 — LIDAR SubsystemCustom C++ driver + processing + orchestration✅ Complete
3 — ESP32 FirmwareMotor control + sensor fusion + ROS 2 bridge✅ Complete
4 — System OrchestrationFull bringup + health monitoring + recovery✅ Complete
4.5 — AI AssistantGemma 3 270M GGUF + LoRA + conversation orchestrator✅ Complete
2 — NavigationNav2, SLAM, AMCL, waypoint navigationPlanned
5 — Display & UXTouchscreen UI for passengersPlanned
6 — Cross-compilationDocker multi-arch (amd64 + arm64)Planned
7 — OTA & SecurityDDS security, MCUboot, encrypted commsPlanned
8 — SimulationGazebo Ignition + URDFPlanned
9 — MVP DemoAutonomous luggage carrying demoTarget: ~10 weeks

License

  • ydlidar_driver — Apache 2.0 (open-source)
  • porter_lidar_processor, porter_orchestrator, porter_ai_assistant, porter_esp32_bridge — Proprietary (VirtusCo)
  • ESP32 firmware (esp32_firmware/) — Proprietary (VirtusCo)

VirtusCovirtusco.in · GitHub

About

Autonomous luggage-carrying robot for airports — ROS 2 Jazzy, ESP32 Zephyr firmware, on-device AI assistant, Flutter GUI. Runs fully on Raspberry Pi 5, no cloud.

Topics

Resources

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages