← All modules

5.3 Platform Overview

Draft — not verified

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:

ModeFrontendBackendRound-trip latency
Simulationpnpm devNone requiredN/A (client-side only)
ROS 2 Virtualpnpm devDocker or native0.1–0.5 ms
Hardware Pi 4Bpnpm devNative on Pi 4B0.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.

ComposablePlatform pageKey responsibility
useControlAlgorithms/controlP / PID / Pure Pursuit / State Machine
useObstacleAvoidance/controlPotential Fields / DWA / VFH / E-Stop
useMobileRobotSimulation/controlDifferential drive physics, collision
useSLAMNavigation/slam-navigationslam_toolbox / cartographer / sim
useFigureEightNavigation/figure-eight-navLemniscate trajectory + Pure Pursuit
useMaterialHandling/material-handlingWarehouse pick-and-place FSM
useROSBridgeall ROS 2 pagesWebSocket pub/sub to rosbridge
useMobileRobotWithROS2all ROS 2 pagesBidirectional ROS 2 mode
useRobotConnectionall pages4-mode connection abstraction
useAnalyticsEngine/analyticsStats collection, CSV/JSON export
useLatencyDashboard/latencyCommand round-trip measurement
useAlgorithmComparison/controlSide-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, wheel­Base = 0.16 m, wheelRadius = 0.033 m, collision detection
  • FigureEightTrajectory — Lemniscate parametric path generation, arc-length parameterisation, waypoint sampling
  • LaserScan — Polar-to-Cartesian conversion, range filtering, sector analysis
  • SLAMMap — Occupancy grid with log-odds update, coverage calculation
  • WarehouseEnvironment — Shelf layout, station positions, collision zones for the material handling simulation
  • MaterialObject — Pick-and-place item with position, weight, and destination metadata
  • PickAndPlaceTask — 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

TopicMessage typeDirectionHz
/cmd_velgeometry_msgs/TwistFrontend Backend10–50
/stamped_cmd_velstd_msgs/String (JSON)Frontend Backend10–50
/odomnav_msgs/OdometryBackend Frontend20
/scansensor_msgs/LaserScanBackend Frontend10
/tftf2_msgs/TFMessageBackend Frontend20
/robot_infostd_msgs/String (JSON)Backend Frontend1
/latency_reportsstd_msgs/String (JSON)Backend Frontendon-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)

URLSimulation modeKey features
/DashboardMode selector, grouped navigation
/manualManual controlJoystick + WASD, live latency HUD
/controlAuto control4 algorithms, 4 avoidance, env. density
/slam-navigationSLAMOccupancy grid, auto-explore
/figure-eight-navTrajectoryFigure-8 + Pure Pursuit controller
/latencyLatencyCommand-to-execute timing dashboard
/analyticsAnalyticsStats, sparklines, CSV/JSON export
/material-handlingWarehousePick-and-place simulation
/ros2-testROS 2 test benchTopic pub/sub, live telemetry
/guideReferenceArchitecture, parameter reference
/adminAdminTuned parameter presets (password)
/mobile-robot-ros2Mobile ROS 2Compact 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
  • TypeScriptlang="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, never ros: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

TargetFrontendBackend
Localhostpnpm dev./start.sh start
Pi headlessRun on laptopinstall.sh systemd on Pi
Pi self-containedpnpm build && node .output/...install.sh systemd on Pi
Cloud (chirpstack)./deploy-cloud.sh fullDocker 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.