diff --git a/CLAUDE.md b/CLAUDE.md index 02a93ce..7ca3b92 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,8 @@ pixi run submodules # Fetch git submodules (sllidar_ros2, kinematic_icp) pixi run launch # Full robot bringup (hardware + lidar + camera + localization) pixi run slam # SLAM stack only (run alongside launch) pixi run nav # Nav2 stack (requires a saved map at ~/.mote/map.yaml) -pixi run robot # mote_launch + slam together +pixi run mapping # bringup + SLAM together (build/extend a map) +pixi run robot # bringup + Nav2 together (drive a saved map; needs ~/.mote/map.yaml) pixi run save-map # Save current map to ~/.mote/map pixi run teleop # Keyboard teleoperation pixi run sync # rsync project to Pi at SSH host 'mote' @@ -65,9 +66,10 @@ Contains `urdf/mote.urdf.xacro` and `config/robot.yaml`. The xacro loads robot.y Launch files, config, udev rules, and systemd services. **Launch hierarchy:** -- `robot_launch.py` — combines `mote_launch.py` + `slam_launch.py` +- `robot_launch.py` — combines `mote_launch.py` + `nav2_launch.py` (everyday operation: drive a saved map). Forwards a `map` arg, defaulting to `~/.mote/map.yaml`. +- `mapping_launch.py` — combines `mote_launch.py` + `slam_launch.py` (build/extend a map with SLAM) - `mote_launch.py` — main bringup: robot_state_publisher, ros2_control_node, controller spawners, sllidar, laser_filter, v4l2_camera, and `localization_launch.py`. Reads `robot.yaml` for wheel geometry (injected into DiffDriveController params) and sensor config. -- `localization_launch.py` — AMCL-based localization +- `localization_launch.py` — kinematic_icp LIDAR odometry (publishes `odom`→`base`; the map→odom corrector is slam_toolbox when mapping or AMCL when navigating). Despite the name, it does *not* run AMCL — AMCL lives in `nav2_launch.py`. - `slam_launch.py` — slam_toolbox (accepts `use_sim_time:=true` for the sim) - `sim_launch.py` — Gazebo sim (sim environment only): headless gz server with `worlds/mote_world.sdf`, robot spawn, ros_gz bridge (/clock, /scan), controllers, laser_filter. The URDF is processed with `use_sim:=true`, which swaps `MoteHardware` for `gz_ros2_control` and adds a simulated lidar (specs from `robot.yaml` `lidar.sim`). Without that flag the xacro output is unchanged. Controller params are merged into one temp file (gz_ros2_control loads a single `` file referenced in the URDF). - `nav2_launch.py` — Nav2 stack diff --git a/README.md b/README.md index d71a46e..4c71b78 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,10 @@ pixi run rviz # Runs rviz to view the map and navigate pixi run slam # Runs the SLAM stack to create a map # or pixi run nav # Runs the nav stack + +# Convenience combos (base + the relevant stack in one command): +pixi run mapping # = launch + slam (build/extend a map) +pixi run robot # = launch + nav (drive a saved map at ~/.mote/map.yaml) ``` ### Simulation (no hardware required) diff --git a/mote_bringup/launch/mapping_launch.py b/mote_bringup/launch/mapping_launch.py new file mode 100644 index 0000000..eca504c --- /dev/null +++ b/mote_bringup/launch/mapping_launch.py @@ -0,0 +1,25 @@ +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource + + +def generate_launch_description(): + launch_dir = os.path.join( + get_package_share_directory("mote_bringup"), "launch" + ) + + return LaunchDescription([ + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(launch_dir, "mote_launch.py") + ) + ), + IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(launch_dir, "slam_launch.py") + ) + ), + ]) diff --git a/mote_bringup/launch/robot_launch.py b/mote_bringup/launch/robot_launch.py index eca504c..c0e183b 100644 --- a/mote_bringup/launch/robot_launch.py +++ b/mote_bringup/launch/robot_launch.py @@ -2,8 +2,9 @@ from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription -from launch.actions import IncludeLaunchDescription +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration def generate_launch_description(): @@ -11,7 +12,14 @@ def generate_launch_description(): get_package_share_directory("mote_bringup"), "launch" ) + default_map = os.path.join(os.path.expanduser("~"), ".mote", "map.yaml") + return LaunchDescription([ + DeclareLaunchArgument( + "map", + default_value=default_map, + description="Full path to the map yaml file Nav2 should load", + ), IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join(launch_dir, "mote_launch.py") @@ -19,7 +27,8 @@ def generate_launch_description(): ), IncludeLaunchDescription( PythonLaunchDescriptionSource( - os.path.join(launch_dir, "slam_launch.py") - ) + os.path.join(launch_dir, "nav2_launch.py") + ), + launch_arguments={"map": LaunchConfiguration("map")}.items(), ), ]) diff --git a/pixi.toml b/pixi.toml index 19c994f..a2e9eec 100644 --- a/pixi.toml +++ b/pixi.toml @@ -13,11 +13,12 @@ build = { cmd = "colcon build --symlink-install --cmake-args -G Ninja -DCMAKE_PO clean = "pkill -9 -f 'robot_state_publisher|ros2_control_node|sllidar|v4l2_camera|spawner'; ros2 daemon stop; rm -f /dev/shm/fast*; ros2 daemon start; true" install-systemd = "bash mote_bringup/systemd/install.sh" launch = "ros2 launch mote_bringup mote_launch.py" +mapping = "ros2 launch mote_bringup mapping_launch.py" nav = "ros2 launch mote_bringup nav2_launch.py map:=$HOME/.mote/map.yaml" robot = "ros2 launch mote_bringup robot_launch.py" save-map = "mkdir -p $HOME/.mote && ros2 run nav2_map_saver map_saver_cli -f $HOME/.mote/map" slam = "ros2 launch mote_bringup slam_launch.py" -sync = "rsync -avz --exclude='.pixi/' --exclude='build/' --exclude='install/' --exclude='log/' --exclude='.git/' --exclude='.claude/' . michael@mote:~/Mote/" +sync = "rsync -avz --exclude='.pixi/' --exclude='build/' --exclude='install/' --exclude='log/' --exclude='.git/' --exclude='.claude/' . michael@auldbot:~/Mote/" teleop = "ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -p stamped:=true -r /cmd_vel:=/diff_drive_controller/cmd_vel" udev = "sudo cp mote_bringup/udev/99-mote.rules /etc/udev/rules.d/ && sudo udevadm control --reload-rules && sudo udevadm trigger" setup-ids = "ros2 run mote_hardware setup_ids"