Platform Reference: ArbiterROS Mobile Robot Intelligence Platform
Module 5 Reference: ArbiterROS Architecture
Learning Objectives
- Understand the full ArbiterROS platform architecture and component relationships
- Navigate the 12 composables and 7 physics libraries by function
- Select the correct connection mode for each deployment scenario
- Interpret ROS 2 topic flows between frontend and backend
- Apply the platform design conventions when extending the codebase
5.3.1 Platform Overview
ArbiterROS (https://arbiter.txio.live) is a Nuxt 3 + ROS 2 Humble mobile robot platform providing 3D simulation, four control algorithms, obstacle avoidance, SLAM navigation, latency measurement, and physical hardware integration. The platform operates in three connection modes with no code changes required:
| Mode | Frontend | Backend | Round-trip latency |
|---|---|---|---|
| Simulation | pnpm dev | None required | N/A (client-side only) |
| ROS 2 Virtual | pnpm dev | Docker or native | 0.1–0.5 ms |
| Hardware Pi 4B | pnpm dev | Native on Pi 4B | 0.2–0.8 ms |
Top-Level Structure:
ros2-mobile-platform/
frontend/ Nuxt 3 app (pnpm, Vue 3, TypeScript)
composables/Robot/ 12 composables — all robot logic lives here
lib/Robot/ 7 physics/math libraries (plain JS, no framework)
components/Simulations/ Vue 3D components (Three.js)
pages/ 12 pages — one per simulation mode
tests/ 169 vitest tests
backend/ ROS 2 Python nodes
robots/ virtual_robot, hardware_robot, latency_tracker
drivers/ mock, l298n, serial, pigpio
sensors/ base_lidar, rplidar
launch/ robot_bringup, hardware_bringup
config/ SLAM, Nav2, hardware params
docker-compose.yml Full-stack local deployment
docker-compose.pi.yml Raspberry Pi 4B deployment
deploy-cloud.sh One-command deploy to DigitalOcean
deploy-pi.sh One-command deploy to Pi 4B
5.3.2 Connection Architecture
The useRobotConnection composable abstracts all four connection modes behind a
single interface. Switching modes disconnects the current bridge and reconnects automatically:
// useRobotConnection.ts
export type ConnectionMode =
'simulation' | 'ros2-virtual' | 'ros2-hardware' | 'ros2-pi4b'
const switchMode = async (newMode: ConnectionMode) => {
// Disconnect previous mode
if (mode.value !== 'simulation' && ros2Robot.ros2Enabled.value)
ros2Robot.disableROS2()
if (bridge.isConnected.value) bridge.disconnect()
mode.value = newMode
if (newMode === 'simulation') return
// Connect to rosbridge WebSocket
bridge.connect({
url: bridgeUrl.value,
autoReconnect: true,
reconnectInterval: 3000,
maxReconnectAttempts: 10,
})
}
When a connection is established, the composable automatically enables bidirectional ROS 2
mode and subscribes to /robot_info to populate hardware capability metadata:
// Auto-enable ROS2 robot when connection established
watch(() => bridge.isConnected.value, (connected) => {
if (connected && isROS2.value) {
ros2Robot.enableROS2('bidirectional')
bridge.subscribeTopic<{ data: string }>(
{ name: '/robot_info', messageType: 'std_msgs/String' },
(msg) => { robotInfo.value = JSON.parse(msg.data) }
)
}
})
5.3.3 Frontend Composables (12)
All robot intelligence lives in frontend/composables/Robot/. Each composable is a
self-contained Vue 3 Composition API module with no cross-dependencies except where
explicitly imported.
| Composable | Platform page | Key responsibility |
|---|---|---|
useControlAlgorithms | /control | P / PID / Pure Pursuit / State Machine |
useObstacleAvoidance | /control | Potential Fields / DWA / VFH / E-Stop |
useMobileRobotSimulation | /control | Differential drive physics, collision |
useSLAMNavigation | /slam-navigation | slam_toolbox / cartographer / sim |
useFigureEightNavigation | /figure-eight-nav | Lemniscate trajectory + Pure Pursuit |
useMaterialHandling | /material-handling | Warehouse pick-and-place FSM |
useROSBridge | all ROS 2 pages | WebSocket pub/sub to rosbridge |
useMobileRobotWithROS2 | all ROS 2 pages | Bidirectional ROS 2 mode |
useRobotConnection | all pages | 4-mode connection abstraction |
useAnalyticsEngine | /analytics | Stats collection, CSV/JSON export |
useLatencyDashboard | /latency | Command round-trip measurement |
useAlgorithmComparison | /control | Side-by-side algorithm benchmarking |
5.3.4 Physics Libraries (7)
Pure JavaScript classes in frontend/lib/Robot/ — no Vue reactivity, fully
testable in isolation via vitest.
MobileRobotPhysics— Differential drive kinematic model, wheelBase = 0.16 m, wheelRadius = 0.033 m, collision detectionFigureEightTrajectory— Lemniscate parametric path generation, arc-length parameterisation, waypoint samplingLaserScan— Polar-to-Cartesian conversion, range filtering, sector analysisSLAMMap— Occupancy grid with log-odds update, coverage calculationWarehouseEnvironment— Shelf layout, station positions, collision zones for the material handling simulationMaterialObject— Pick-and-place item with position, weight, and destination metadataPickAndPlaceTask— Task queue manager with priority scheduling
MobileRobotPhysics core parameters:
// MobileRobotPhysics.js
class MobileRobotPhysics {
constructor(options = {}) {
this.wheelRadius = options.wheelRadius || 0.033 // m
this.wheelBase = options.wheelBase || 0.16 // m
// Kinematic model for differential drive
// update(v, omega, dt) integrates pose using Euler method
// setObstacles(obstacles) registers circular collision zones
}
}
5.3.5 Backend Nodes
virtual_robot.py — VirtualRobotWithSLAM
Simulates a differential-drive robot in a 16 m 16 m bounded arena. Parameters:
- Wheel base: 0.30 m; max linear/angular velocity: 2.0 m/s and 2.0 rad/s
- 360° LiDAR: 1° angular resolution, 0.1–10 m range, published at 10 Hz
- 8 circular obstacles (radii 0.3–0.6 m) and 4 boundary walls
- Publishes:
/odom(20 Hz),/scan(10 Hz),/tf - Subscribes:
/cmd_vel
autonomous_behavior.py — AutonomousBehavior
Six-state behaviour FSM. See Module 6.1 for full documentation.
hardware_robot.py — HardwareRobot
Driver abstraction factory that dynamically loads one of four motor drivers at runtime:
mock l298n serial pigpio. See Module 6.5 for
full documentation.
latency_tracker.py — LatencyTracker
Intercepts /stamped_cmd_vel, measures command-to-execution latency in
milliseconds, and exposes results via HTTP API on port 8085. See Module 6.3 for full
documentation.
5.3.6 ROS 2 Topics
| Topic | Message type | Direction | Hz |
|---|---|---|---|
/cmd_vel | geometry_msgs/Twist | Frontend Backend | 10–50 |
/stamped_cmd_vel | std_msgs/String (JSON) | Frontend Backend | 10–50 |
/odom | nav_msgs/Odometry | Backend Frontend | 20 |
/scan | sensor_msgs/LaserScan | Backend Frontend | 10 |
/tf | tf2_msgs/TFMessage | Backend Frontend | 20 |
/robot_info | std_msgs/String (JSON) | Backend Frontend | 1 |
/latency_reports | std_msgs/String (JSON) | Backend Frontend | on-demand |
Service Ports:
- :3000 — Nuxt 3 frontend
- :9090 — ROSBridge WebSocket (
ws://host:9090) - :8085 — Latency API (
GET /api/latency,GET /api/health)
5.3.7 Platform Pages (12)
| URL | Simulation mode | Key features |
|---|---|---|
/ | Dashboard | Mode selector, grouped navigation |
/manual | Manual control | Joystick + WASD, live latency HUD |
/control | Auto control | 4 algorithms, 4 avoidance, env. density |
/slam-navigation | SLAM | Occupancy grid, auto-explore |
/figure-eight-nav | Trajectory | Figure-8 + Pure Pursuit controller |
/latency | Latency | Command-to-execute timing dashboard |
/analytics | Analytics | Stats, sparklines, CSV/JSON export |
/material-handling | Warehouse | Pick-and-place simulation |
/ros2-test | ROS 2 test bench | Topic pub/sub, live telemetry |
/guide | Reference | Architecture, parameter reference |
/admin | Admin | Tuned parameter presets (password) |
/mobile-robot-ros2 | Mobile ROS 2 | Compact ROS 2 control view |
5.3.8 Key Design Decisions
These decisions are canonical for all development on ArbiterROS. Violations cause visual or functional bugs.
Coordinate Mapping (Three.js):
// Physics X-axis -> Three.js X-axis (no negation)
// Physics Y-axis -> Three.js Z-axis (no negation)
// Robot orientation: robotMesh.rotation.y = -theta (negate ONLY theta)
robot.position.set(state.x, 0, state.y) // CORRECT
robot.position.set(state.x, 0, -state.y) // WRONG — causes rotation drift
Analytics State (Singleton):
The analytics engine uses module-level reactive refs so data persists across page navigation:
// useAnalyticsEngine.ts — module-level, not inside the composable function
const _isCollecting = ref(false)
const _dataPoints = ref<DataPoint[]>([])
const _collectionRate = ref(10) // Hz
Simulated LiDAR:
The simulation composable generates a 72-ray LiDAR (5° spacing) via ray-circle
intersection. This feeds useObstacleAvoidance identically to a real
/scan message:
// 72 rays at 5-degree spacing: full 360-degree coverage
const LIDAR_RAYS = 72
const ANGLE_STEP = (2 * Math.PI) / LIDAR_RAYS
Coding Rules (from CLAUDE.md):
- pnpm only — never npm or yarn
- TypeScript —
lang="ts"on all Vue<script setup>blocks - Vue 3 Composition API only — no Options API
- Nuxt 3 auto-imports — do not manually import
ref,computed,onMounted - ROS 2 base image — use
ros:humble, neverros:humble-ros-base(ros2cli is broken in the base image)
5.3.9 Quick Start
# Simulation only (no ROS2 required)
cd frontend && pnpm install && pnpm dev
# Full stack — frontend + virtual robot backend
docker compose up --build
# Backend only (macOS requires Docker; Linux/Pi can run native)
cd backend && ./start.sh start
./start.sh status # health + latency check
./start.sh logs # tail logs
# Physical robot on Pi 4B
cd backend && sudo ./install.sh systemd --hardware
sudo systemctl start ros2-robot.target
# Run tests
cd frontend && pnpm test:run # 169 tests, ~3s
5.3.10 Deployment Targets
| Target | Frontend | Backend |
|---|---|---|
| Localhost | pnpm dev | ./start.sh start |
| Pi headless | Run on laptop | install.sh systemd on Pi |
| Pi self-contained | pnpm build && node .output/... | install.sh systemd on Pi |
| Cloud (chirpstack) | ./deploy-cloud.sh full | Docker Compose on VPS |
Production deployment uses docker-compose.prod.yml with
jwilder/nginx-proxy + letsencrypt for automatic TLS termination.
The chirpstack server is at 165.22.241.5 with domain arbiter.txio.live.