← All modules

6.1 Autonomous Behaviour

Draft — not verified

Theoretical Background: Autonomous Behaviour and Mission Execution

Module 6 Theory: Autonomous Behaviour and Mission Execution

Learning Objectives

  • Understand the six-state BehaviorMode FSM in autonomous_behavior.py
  • Analyse state transition logic and the role of sensor data in mode switching
  • Study the wall-following and exploration algorithms at the behaviour level
  • Understand the pick-and-place task FSM in useMaterialHandling
  • Connect behaviour-based execution to the broader integrated system (6.2–6.4)

6.1.1 Behaviour-Based Architecture

ArbiterROS implements a behaviour-based architecture at the backend. Rather than a single monolithic controller, autonomous_behavior.py defines six discrete modes that are selected based on sensor state and operator commands. This separates concerns cleanly: each behaviour encapsulates its own sensor processing and control law, and the mode-switching logic is centralised and auditable.

BehaviorMode Enumeration:

# autonomous_behavior.py
class BehaviorMode(Enum):
    IDLE              = "idle"
    MANUAL            = "manual"
    OBSTACLE_AVOIDANCE = "obstacle_avoidance"
    FOLLOW_WALL       = "follow_wall"
    EXPLORE           = "explore"
    GO_TO_GOAL        = "go_to_goal"

Configurable Parameters:

self.declare_parameter('behavior_mode',      'obstacle_avoidance')
self.declare_parameter('max_linear_speed',   0.5)   # m/s
self.declare_parameter('max_angular_speed',  1.0)   # rad/s
self.declare_parameter('obstacle_threshold', 0.5)   # m
self.declare_parameter('goal_tolerance',     0.1)   # m
self.declare_parameter('wall_follow_distance', 0.3) # m
self.declare_parameter('exploration_timeout', 30.0) # s

6.1.2 State Definitions and Transition Conditions

IDLE

The robot is stationary. No velocity commands are published. The node waits for a mode-change message on /behavior_mode or a goal on /move_base_simple/goal.

Transitions out:

  • /behavior_mode message received set corresponding mode
  • Goal received on /move_base_simple/goal go_to_goal

MANUAL

The node forwards /cmd_vel commands from the web dashboard without modification. No sensor processing or autonomous control is applied.

OBSTACLE_AVOIDANCE

Reactive avoidance using LiDAR sector analysis. The front sector is scanned for obstacles within obstacle_threshold. When clear, the robot drives forward; when blocked, it rotates away from the nearest obstacle.

def _obstacle_avoidance_control(self):
    if self.laser_data is None:
        return Twist()


ranges = self.laser_data.ranges front_idx = len(ranges) // 2 sector = 30 # degrees each side

Minimum distance in front arc

front = r for r in rangesfront_idx-sector:front_idx+sector if self.laser_data.range_min < r < self.laser_data.range_max min_front = min(front) if front else float('inf')



    cmd = Twist()
    if min_front > self.obstacle_threshold:
        cmd.linear.x  = self.max_linear_speed
    else:
        # Turn away from nearest obstacle
        left_mean  = self._sector_mean(ranges, front_idx+sector,
                                       front_idx+2*sector)
        right_mean = self._sector_mean(ranges, front_idx-2*sector,
                                       front_idx-sector)
        cmd.angular.z = self.max_angular_speed \
                        if left_mean > right_mean else -self.max_angular_speed
    return cmd

FOLLOW_WALL

The robot maintains a fixed lateral distance from the nearest wall surface. A PD controller drives the robot to keep the right-side sensor reading at wall_follow_distance:

EXPLORE

Random-walk exploration with collision avoidance. The robot selects a random heading when it encounters an obstacle or when the exploration_timeout elapses, then drives in that direction until the next event.

def _explore_control(self):
    now  = self.get_clock().now().to_msg().sec
    elapsed = now - self.exploration_start_time

    cmd = Twist()
    if (self._is_path_clear() and
            elapsed < self.exploration_timeout):
        cmd.linear.x = self.max_linear_speed * 0.7
    else:
        # Choose new random heading
        import random
        cmd.angular.z = random.uniform(
            -self.max_angular_speed, self.max_angular_speed)
        self.exploration_start_time = now
    return cmd

GO_TO_GOAL

Proportional heading controller driving to a geometry_msgs/Point goal received on /move_base_simple/goal:

def _go_to_goal_control(self):
    if self.goal_point is None or self.robot_pose is None:
        return Twist()


dx = self.goal_point.x - self.robot_pose.x dy = self.goal_point.y - self.robot_pose.y dist = math.sqrt(dx2 + dy2)

if dist < self.goal_tolerance: self.current_mode = BehaviorMode.IDLE return Twist()

desired = math.atan2(dy, dx) error = desired - self.robot_pose.theta while error > math.pi: error -= 2math.pi while error < -math.pi: error += 2math.pi



    cmd = Twist()
    cmd.angular.z = max(-self.max_angular_speed,
                    min( self.max_angular_speed, 2.0 * error))
    if abs(error) < 0.3:
        cmd.linear.x = min(self.max_linear_speed, 0.5 * dist)
    return cmd

6.1.3 ROS 2 Interface

Subscriptions:

  • /behavior_mode (std_msgs/String) — mode name string
  • /odom (nav_msgs/Odometry) — robot pose for GO_TO_GOAL
  • /scan (sensor_msgs/LaserScan) — laser data for all reactive modes
  • /move_base_simple/goal (geometry_msgs/Point) — navigation goal

Publications:

  • /cmd_vel_auto (geometry_msgs/Twist) — autonomous velocity commands
  • /robot_status (std_msgs/String) — current mode and state string

The web dashboard (6.2) publishes to /behavior_mode to switch states remotely, and subscribes to /robot_status to display the active behaviour in the UI.

6.1.4 Mission-Level Execution: Material Handling

The /material-handling page implements a higher-level mission FSM built on top of the robot’s low-level motion primitives. useMaterialHandling.ts uses PickAndPlaceTask and TaskQueueManager from the physics library to model a warehouse pick-and-place mission.

Mission States:

  1. IDLE — waiting for task queue to populate
  2. NAVIGATE_TO_PICKUP — drive to material source station
  3. PICK — align and collect material object
  4. NAVIGATE_TO_DROPOFF — drive to destination station
  5. PLACE — align and deposit material object
  6. RETURN — return to home position or next pickup
// useMaterialHandling.ts (simplified state machine)
const executeMissionStep = async () => {
  switch (missionState.value) {
    case 'NAVIGATE_TO_PICKUP':
      await navigateTo(currentTask.pickupStation)
      missionState.value = 'PICK'
      break


case 'PICK': await alignAndCollect(currentTask.material) missionState.value = 'NAVIGATE_TO_DROPOFF' break

case 'NAVIGATE_TO_DROPOFF': await navigateTo(currentTask.dropoffStation) missionState.value = 'PLACE' break



    case 'PLACE':
      await alignAndDeposit(currentTask.material)
      metrics.tasksCompleted++
      missionState.value = queue.hasNext() ? 'NAVIGATE_TO_PICKUP' : 'IDLE'
      break
  }
}

TaskQueueManager and Priority Scheduling:

// lib/Robot/PickAndPlaceTask.js
class TaskQueueManager {
  addTask(task, priority = 'normal')  // 'urgent' | 'normal' | 'low'
  getNextTask()                        // returns highest-priority pending task
  markComplete(taskId)
  getStats()                           // { total, completed, pending, failed }
}

HandlingMetrics Interface:

// useMaterialHandling.ts
interface HandlingMetrics {
  tasksCompleted:  number   // successful pick-and-place cycles
  totalDistance:   number   // odometric path length (m)
  averageCycleTime: number  // mean time per task (s)
  efficiency:      number   // tasks / totalDistance  (tasks/m)
  collisions:      number   // obstacle contacts during mission
}

Integration: Theory to Practice

In the integrated system, autonomous_behavior.py provides the autonomous execution layer that the web dashboard (6.2) switches between via /behavior_mode topic messages. Voice commands (if implemented) would resolve to the same mode-switching calls. The Docker deployment (6.4) runs autonomous_behavior.py as a separate process alongside the virtual robot node, allowing the behaviour FSM to run independently of the ROS-to-web bridge.

The material handling FSM at the frontend level (useMaterialHandling.ts) operates in simulation mode without requiring a ROS 2 backend, demonstrating how complex mission logic can be prototyped entirely in the browser before deployment to hardware.

Theoretical Design Choices

Why a flat FSM over a behaviour tree? For six states with linear transitions, a flat FSM is easier to debug and audit than a behaviour tree. Each mode’s transition condition is a single conditional check on sensor data, making the control flow entirely predictable. Behaviour trees become advantageous when the number of recovery sequences and conditional branches grows beyond approximately ten nodes.

Why separate /cmd_vel_auto from /cmd_vel? Publishing autonomous commands to a separate topic allows the web dashboard to arbitrate between manual and autonomous commands cleanly. The dashboard subscribes to both topics and applies a priority rule: manual joystick input always overrides /cmd_vel_auto by publishing directly to /cmd_vel. This avoids race conditions without requiring a mutex in Python.

Why exploration uses timeout rather than frontier detection? Full frontier-based exploration requires an occupancy grid with consistent coordinate frames. The random-walk with timeout provides functional exploration behaviour without the computational overhead of frontier extraction, making it suitable for educational demonstration on resource-constrained hardware.