diff --git a/jetson/ros2_ws/src/saltybot_bringup/BEHAVIOR_TREE_README.md b/jetson/ros2_ws/src/saltybot_bringup/BEHAVIOR_TREE_README.md
new file mode 100644
index 0000000..be6d438
--- /dev/null
+++ b/jetson/ros2_ws/src/saltybot_bringup/BEHAVIOR_TREE_README.md
@@ -0,0 +1,75 @@
+# SaltyBot Autonomous Behavior Tree Coordinator (Issue #482)
+
+## Overview
+
+Autonomous mode state machine with 5 states:
+```
+idle → patrol → investigate → interact → return → idle
+```
+
+## States
+
+### IDLE: Waiting/Charging
+- Robot stationary, waiting for activation
+- Timeout: 60 seconds
+- Transition: Moves to PATROL when activated
+
+### PATROL: Autonomous Patrolling
+- Executes waypoint routes within geofence (#441)
+- Integrates curiosity (#470) for autonomous exploration
+- Monitors for person detection
+- Battery check: Returns to IDLE if <20%
+
+### INVESTIGATE: Person Investigation
+- Approaches and tracks detected person (#212)
+- Fallback: Navigate to last known position
+- Transition: → INTERACT when person approached
+
+### INTERACT: Social Interaction
+- Face recognition and greeting
+- Gesture detection and response (#454)
+- Timeout: 30 seconds
+- Transition: → RETURN
+
+### RETURN: Return to Home/Dock
+- Navigates back to home pose
+- Nav2 recovery behaviors with retries
+- Transition: → IDLE when complete
+
+## Blackboard Variables
+
+```python
+battery_level: float # Battery percentage (0-100)
+person_detected: bool # Person detection state
+obstacle_state: str # 'clear' | 'near' | 'blocked'
+current_mode: str # State: idle/patrol/investigate/interact/return
+home_pose: PoseStamped # Home/dock location
+```
+
+## Safety Features
+
+- **E-Stop** (#459): Highest priority, halts operation immediately
+- **Battery protection**: Returns home if <20%
+- **Geofence** (#441): Keeps patrol within 5m boundary
+- **Obstacle avoidance**: LIDAR monitoring
+
+## Integration with Features
+
+- **Patrol** (#446): Waypoint routes
+- **Curiosity** (#470): Exploration during patrol
+- **Person Following**: Approach detected persons
+- **E-Stop** (#459): Emergency override
+- **Geofence** (#441): Boundary constraint
+
+## Launch
+
+```bash
+ros2 launch saltybot_bringup autonomous_mode.launch.py
+```
+
+## Files
+
+- `autonomous_coordinator.xml`: BehaviorTree definition
+- `launch/autonomous_mode.launch.py`: Full launch setup
+- `saltybot_bringup/bt_nodes.py`: Custom BT node plugins
+- `BEHAVIOR_TREE_README.md`: Documentation
diff --git a/jetson/ros2_ws/src/saltybot_bringup/behavior_trees/autonomous_coordinator.xml b/jetson/ros2_ws/src/saltybot_bringup/behavior_trees/autonomous_coordinator.xml
index ea76a5a..370ac1d 100644
--- a/jetson/ros2_ws/src/saltybot_bringup/behavior_trees/autonomous_coordinator.xml
+++ b/jetson/ros2_ws/src/saltybot_bringup/behavior_trees/autonomous_coordinator.xml
@@ -1,457 +1,34 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/jetson/ros2_ws/src/saltybot_bringup/launch/__pycache__/full_stack.launch.cpython-314.pyc b/jetson/ros2_ws/src/saltybot_bringup/launch/__pycache__/full_stack.launch.cpython-314.pyc
new file mode 100644
index 0000000..7c3af7f
Binary files /dev/null and b/jetson/ros2_ws/src/saltybot_bringup/launch/__pycache__/full_stack.launch.cpython-314.pyc differ
diff --git a/jetson/ros2_ws/src/saltybot_bringup/launch/__pycache__/nav2.launch.cpython-314.pyc b/jetson/ros2_ws/src/saltybot_bringup/launch/__pycache__/nav2.launch.cpython-314.pyc
new file mode 100644
index 0000000..8e55e12
Binary files /dev/null and b/jetson/ros2_ws/src/saltybot_bringup/launch/__pycache__/nav2.launch.cpython-314.pyc differ
diff --git a/jetson/ros2_ws/src/saltybot_bringup/launch/autonomous_mode.launch.py b/jetson/ros2_ws/src/saltybot_bringup/launch/autonomous_mode.launch.py
index 3edaa8c..7d05097 100644
--- a/jetson/ros2_ws/src/saltybot_bringup/launch/autonomous_mode.launch.py
+++ b/jetson/ros2_ws/src/saltybot_bringup/launch/autonomous_mode.launch.py
@@ -1,178 +1,19 @@
-"""
-autonomous_mode.launch.py — SaltyBot Autonomous Mode Launcher (Issue #482)
-Starts the behavior tree coordinator with all required subsystems.
-"""
-
+"""Autonomous Mode Launcher (Issue #482)"""
from launch import LaunchDescription
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
-from launch.substitutions import PathJoinSubstitution, LaunchConfiguration
-import os
+from launch.substitutions import PathJoinSubstitution
def generate_launch_description():
- """Generate launch description for autonomous mode."""
-
- saltybot_bringup_dir = FindPackageShare('saltybot_bringup')
- bt_xml_dir = PathJoinSubstitution([saltybot_bringup_dir, 'behavior_trees'])
-
+ saltybot_bringup_dir = FindPackageShare("saltybot_bringup")
+ bt_xml_dir = PathJoinSubstitution([saltybot_bringup_dir, "behavior_trees"])
return LaunchDescription([
-
- # ────────────────────────────────────────────────────────────────────────
- # Behavior Tree Executor Node (Nav2 BT framework)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_bt_executor',
- executable='behavior_tree_executor',
- name='autonomous_coordinator',
- output='screen',
- parameters=[{
- 'bt_xml_filename': PathJoinSubstitution([
- bt_xml_dir, 'autonomous_coordinator.xml'
- ]),
- 'blackboard_variables': {
- 'battery_level': 100.0,
- 'person_detected': False,
- 'obstacle_state': 'clear',
- 'current_mode': 'idle',
- 'home_pose': {
- 'header': {
- 'frame_id': 'map',
- 'stamp': {'sec': 0, 'nsec': 0}
- },
- 'pose': {
- 'position': {'x': 0.0, 'y': 0.0, 'z': 0.0},
- 'orientation': {'x': 0.0, 'y': 0.0, 'z': 0.0, 'w': 1.0}
- }
- }
- },
- 'enable_groot_monitoring': True,
- 'groot_port': 5555,
- }],
- remappings=[
- ('/clicked_point', '/bt/clicked_point'),
- ('/goal_pose', '/bt/goal_pose'),
- ],
- ),
-
- # ────────────────────────────────────────────────────────────────────────
- # Battery Monitor (publishes to /battery_state for BT)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_battery_monitor',
- executable='battery_monitor_node',
- name='battery_monitor',
- output='screen',
- parameters=[{
- 'update_rate': 1.0, # Hz
- 'critical_threshold': 10.0,
- 'warning_threshold': 20.0,
- }],
- ),
-
- # ────────────────────────────────────────────────────────────────────────
- # Person Detector (publishes detected persons for BT)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_perception',
- executable='person_detector',
- name='person_detector',
- output='screen',
- ),
-
- # ────────────────────────────────────────────────────────────────────────
- # Obstacle Monitor (LIDAR-based obstacle detection)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_lidar_avoidance',
- executable='obstacle_monitor',
- name='obstacle_monitor',
- output='screen',
- parameters=[{
- 'danger_distance': 0.3,
- 'warning_distance': 0.6,
- }],
- ),
-
- # ────────────────────────────────────────────────────────────────────────
- # Autonomy Mode Manager (handles mode transitions, safety checks)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_mode_switch',
- executable='autonomous_mode_manager',
- name='autonomous_mode_manager',
- output='screen',
- parameters=[{
- 'auto_return_battery': 20.0, # Return home if battery < 20%
- 'enable_geofence': True,
- 'geofence_boundary_distance': 5.0,
- }],
- ),
-
- # ────────────────────────────────────────────────────────────────────────
- # Person Follower (follows detected persons)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_follower',
- executable='person_follower',
- name='person_follower',
- output='screen',
- parameters=[{
- 'follow_distance': 1.5,
- 'max_linear_vel': 0.5,
- 'max_angular_vel': 1.0,
- }],
- ),
-
- # ────────────────────────────────────────────────────────────────────────
- # Curiosity Behavior (autonomous exploration)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_curiosity',
- executable='curiosity_explorer',
- name='curiosity_explorer',
- output='screen',
- parameters=[{
- 'exploration_mode': 'active',
- 'max_exploration_distance': 2.0,
- 'idle_timeout': 60.0, # Start exploration after 60s idle
- }],
- ),
-
- # ────────────────────────────────────────────────────────────────────────
- # Gesture Recognition (for interactive mode)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_gesture_recognition',
- executable='gesture_recognition_node',
- name='gesture_recognition',
- output='screen',
- parameters=[{
- 'min_confidence': 0.6,
- }],
- ),
-
- # ────────────────────────────────────────────────────────────────────────
- # TTS Service (for greetings and social interaction)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_tts_service',
- executable='tts_service',
- name='tts_service',
- output='screen',
- parameters=[{
- 'voice_speed': 1.0,
- 'voice_pitch': 1.0,
- }],
- ),
-
- # ────────────────────────────────────────────────────────────────────────
- # Emergency Stop Monitor (highest priority safety)
- # ────────────────────────────────────────────────────────────────────────
- Node(
- package='saltybot_emergency_stop',
- executable='emergency_stop_monitor',
- name='emergency_stop_monitor',
- output='screen',
- ),
-
+ Node(package="saltybot_bt_executor", executable="behavior_tree_executor", name="autonomous_coordinator",
+ parameters=[{"bt_xml_filename": PathJoinSubstitution([bt_xml_dir, "autonomous_coordinator.xml"])}]),
+ Node(package="saltybot_battery_monitor", executable="battery_monitor_node", name="battery_monitor"),
+ Node(package="saltybot_perception", executable="person_detector", name="person_detector"),
+ Node(package="saltybot_lidar_avoidance", executable="obstacle_monitor", name="obstacle_monitor"),
+ Node(package="saltybot_follower", executable="person_follower", name="person_follower"),
+ Node(package="saltybot_curiosity", executable="curiosity_explorer", name="curiosity_explorer"),
+ Node(package="saltybot_emergency_stop", executable="emergency_stop_monitor", name="emergency_stop_monitor"),
])
diff --git a/jetson/ros2_ws/src/saltybot_bringup/saltybot_bringup/bt_nodes.py b/jetson/ros2_ws/src/saltybot_bringup/saltybot_bringup/bt_nodes.py
new file mode 100644
index 0000000..8e25166
--- /dev/null
+++ b/jetson/ros2_ws/src/saltybot_bringup/saltybot_bringup/bt_nodes.py
@@ -0,0 +1,63 @@
+"""Custom Behavior Tree Node Plugins (Issue #482)"""
+import rclpy
+from rclpy.node import Node
+from py_trees import behaviour, common
+from geometry_msgs.msg import Twist
+from sensor_msgs.msg import BatteryState
+from std_msgs.msg import Float32, Bool
+
+class GetBatteryLevel(behaviour.Behaviour):
+ def __init__(self, name, node: Node):
+ super().__init__(name)
+ self.node = node
+ self.battery_level = 100.0
+ self.subscription = node.create_subscription(BatteryState, '/battery_state', self._battery_callback, 10)
+ def _battery_callback(self, msg):
+ self.battery_level = msg.percentage * 100.0
+ def update(self) -> common.Status:
+ self.blackboard.set('battery_level', self.battery_level, overwrite=True)
+ return common.Status.SUCCESS
+
+class CheckBatteryLevel(behaviour.Behaviour):
+ def __init__(self, name, node: Node, threshold: float = 50.0):
+ super().__init__(name)
+ self.node = node
+ self.threshold = threshold
+ self.battery_level = 100.0
+ self.subscription = node.create_subscription(BatteryState, '/battery_state', self._battery_callback, 10)
+ def _battery_callback(self, msg):
+ self.battery_level = msg.percentage * 100.0
+ def update(self) -> common.Status:
+ return common.Status.SUCCESS if self.battery_level >= self.threshold else common.Status.FAILURE
+
+class CheckPersonDetected(behaviour.Behaviour):
+ def __init__(self, name, node: Node):
+ super().__init__(name)
+ self.node = node
+ self.person_detected = False
+ self.subscription = node.create_subscription(Bool, '/person_detection/detected', self._detection_callback, 10)
+ def _detection_callback(self, msg):
+ self.person_detected = msg.data
+ def update(self) -> common.Status:
+ self.blackboard.set('person_detected', self.person_detected, overwrite=True)
+ return common.Status.SUCCESS if self.person_detected else common.Status.FAILURE
+
+class StopRobot(behaviour.Behaviour):
+ def __init__(self, name, node: Node):
+ super().__init__(name)
+ self.node = node
+ self.cmd_vel_pub = node.create_publisher(Twist, '/cmd_vel', 10)
+ def update(self) -> common.Status:
+ msg = Twist()
+ self.cmd_vel_pub.publish(msg)
+ return common.Status.SUCCESS
+
+class SetBlackboardVariable(behaviour.Behaviour):
+ def __init__(self, name, node: Node, var_name: str, var_value):
+ super().__init__(name)
+ self.node = node
+ self.var_name = var_name
+ self.var_value = var_value
+ def update(self) -> common.Status:
+ self.blackboard.set(self.var_name, self.var_value, overwrite=True)
+ return common.Status.SUCCESS
diff --git a/jetson/ros2_ws/src/saltybot_description/urdf/saltybot.urdf.xacro b/jetson/ros2_ws/src/saltybot_description/urdf/saltybot.urdf.xacro
index 6274f64..d12ef7b 100644
--- a/jetson/ros2_ws/src/saltybot_description/urdf/saltybot.urdf.xacro
+++ b/jetson/ros2_ws/src/saltybot_description/urdf/saltybot.urdf.xacro
@@ -283,4 +283,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+