diff --git a/.devcontainer.json b/.devcontainer.json index 994e6cf..c3a5a4c 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -11,6 +11,7 @@ "--ipc=host", "--privileged" ], + "containerUser": "cev", "containerEnv": { "DISPLAY": "${localEnv:DISPLAY}" }, @@ -20,10 +21,8 @@ "customizations": { "vscode": { "extensions": [ - "ms-iot.vscode-ros", - "ms-vscode.cpptools-extension-pack", "ms-python.black-formatter", - "xaver.clang-format" + "llvm-vs-code-extensions.vscode-clangd" ], "settings": { "terminal.integrated.defaultProfile.linux": "bash", @@ -33,7 +32,7 @@ } }, "[cpp]": { - "editor.defaultFormatter": "xaver.clang-format" + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd" }, "[python]": { "diffEditor.ignoreTrimWhitespace": false, diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index c3ffaa4..534edf9 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -7,17 +7,28 @@ on: branches: [ "main" ] jobs: - - build: - + build-deploy: runs-on: ubuntu-22.04 - steps: - name: Fetch recursively uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build the Docker image + run: docker build . --file Dockerfile.deploy --tag rc-brain-deploy:$(date +%s) + build-dev: + runs-on: ubuntu-22.04 + steps: + - name: Fetch recursively + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Build the Docker image - run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) + run: docker build . --file Dockerfile.dev --tag rc-brain-dev:$(date +%s) diff --git a/.gitmodules b/.gitmodules index cd316c6..6d65ecd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "cev_msgs"] - path = cev_msgs - url = https://github.com/cornellev/cev_msgs.git [submodule "external/serial-ros2"] path = external/serial-ros2 url = https://github.com/RoverRobotics-forks/serial-ros2.git @@ -13,24 +10,12 @@ [submodule "external/bond"] path = external/bond url = https://github.com/ros/bond_core.git -[submodule "cev/cev_localization_ros2"] - path = cev/cev_localization_ros2 - url = https://github.com/cornellev/cev_localization_ros2.git -[submodule "cev/cev_planner_ros2"] - path = cev/cev_planner_ros2 - url = https://github.com/cornellev/cev_planner_ros2.git -[submodule "cev/cev_autobrake_ros2"] - path = cev/cev_autobrake_ros2 - url = https://github.com/cornellev/cev_autobrake_ros2.git -[submodule "cev/cev_teleop_ros2"] - path = cev/cev_teleop_ros2 - url = https://github.com/cornellev/cev_teleop_ros2.git -[submodule "cev/cev_vision_ros2"] - path = cev/cev_vision_ros2 - url = https://github.com/cornellev/cev_vision_ros2.git -[submodule "cev/cev_encoder_odometry_ros2"] - path = cev/cev_encoder_odometry_ros2 - url = https://github.com/cornellev/cev_encoder_odometry_ros2.git -[submodule "cev/cev_trajectory_follower_ros2"] - path = cev/cev_trajectory_follower_ros2 - url = https://github.com/cornellev/cev_trajectory_follower_ros2.git +[submodule "cev/all/cev_planner_ros2/cev_planner"] + path = cev/all/cev_planner_ros2/cev_planner + url = https://github.com/cornellev/cev_planner.git +[submodule "cev/all/cev_planner_ros2/matplotlib-cpp"] + path = cev/all/cev_planner_ros2/tmp/matplotlib-cpp + url = https://github.com/lava/matplotlib-cpp.git +[submodule "cev/all/cev_localization_ros2/cev_kalman_filter"] + path = cev/all/cev_localization_ros2/cev_kalman_filter + url = https://github.com/cornellev/cev_kalman_filter diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 09c2826..0000000 --- a/Dockerfile +++ /dev/null @@ -1,56 +0,0 @@ -# syntax=docker/dockerfile:1.7-labs -FROM ros:humble - -SHELL ["/bin/bash", "-c"] - -# Arguments -ARG FOLDER_NAME=rc-brain -ARG WORKSPACE_DIR=/home/cev - -# Set working directory -WORKDIR $WORKSPACE_DIR - -# Source ROS2 environment automatically in every shell -RUN echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc -RUN echo "source $WORKSPACE_DIR/install/setup.bash" >> ~/.bashrc - -# Install dependencies -RUN apt-get update && apt-get install -y \ - curl \ - python3-colcon-common-extensions \ - ros-$ROS_DISTRO-ros-base \ - && apt-get clean && rm -rf /var/lib/apt/lists/* - -# Install clang-format -RUN curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc && \ - echo $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy main\n' \ - $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main\n' \ - $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main\n' \ - | tee -a /etc/apt/sources.list && \ - apt-get update && apt-get install -y clang-format clang-format-19 \ - && apt-get clean && rm -rf /var/lib/apt/lists/* - -# Fix libc-bin issue -RUN rm /var/lib/dpkg/info/libc-bin.* || true && apt-get update && apt-get install -y libc-bin - -# Initialize rosdep -RUN rosdep update --rosdistro $ROS_DISTRO - -# Create workspace directory -RUN mkdir -p src/$FOLDER_NAME - -# Copy the full package source code -COPY --parents ./* src/$FOLDER_NAME/ -RUN chmod +x src/$FOLDER_NAME/install.sh && src/$FOLDER_NAME/install.sh - -# Install package dependencies -RUN source /opt/ros/$ROS_DISTRO/setup.bash && rosdep install --from-paths src -r -y - -# Build the ROS2 workspace -RUN source /opt/ros/$ROS_DISTRO/setup.bash && colcon build --symlink-install - -# Set up entrypoint -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile.deploy b/Dockerfile.deploy index 03d49f2..0b8f613 100644 --- a/Dockerfile.deploy +++ b/Dockerfile.deploy @@ -3,6 +3,17 @@ FROM ros:humble SHELL ["/bin/bash", "-c"] +# Set up CEV user +RUN useradd -m -s /bin/bash cev +RUN usermod -aG sudo cev +# Grant passwordless sudo access to the 'sudo' group +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers +USER cev + +# Docker doesn't do this by default +RUN sudo chown -R cev /home/cev + + ARG FOLDER_NAME=rc-brain ARG WORKSPACE_DIR=/home/cev WORKDIR $WORKSPACE_DIR @@ -12,36 +23,31 @@ RUN echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc RUN echo "source $WORKSPACE_DIR/install/setup.bash" >> ~/.bashrc # Init rosdep and update package index -# for some reason we need to do this -RUN rm /var/lib/dpkg/info/libc-bin.* -RUN apt-get clean -RUN apt-get update -RUN apt-get install libc-bin +RUN sudo apt-get update RUN rosdep update --rosdistro $ROS_DISTRO -RUN mkdir -p src/$FOLDER_NAME - # Install and build build-from-source packages # We do this as a separate step from the stuff for our code, since that changes more frequently -RUN cd src && \ - git clone https://github.com/Slamtec/sllidar_ros2.git && \ - cd sllidar_ros2 && \ - git checkout 3430009 +COPY --chown=cev --parents external/**/package.xml src/$FOLDER_NAME/ RUN source /opt/ros/$ROS_DISTRO/setup.bash && rosdep install --from-paths src -r -y -RUN source /opt/ros/$ROS_DISTRO/setup.bash && colcon build -# Install self dependencies with rosdep -COPY --parents ./*/package.xml src/$FOLDER_NAME +# Run install_extra.sh, which installs additional dependencies +COPY --chown=cev scripts/install_extra.sh src/$FOLDER_NAME/scripts/ +RUN sudo src/$FOLDER_NAME/scripts/install_extra.sh + +# Finally build external packages +COPY --chown=cev external src/$FOLDER_NAME/external/ +RUN source /opt/ros/$ROS_DISTRO/setup.bash && colcon build --symlink-install + +# Install all other dependencies +COPY --chown=cev --parents **/package.xml src/$FOLDER_NAME/ RUN source /opt/ros/$ROS_DISTRO/setup.bash && rosdep install --from-paths src -r -y # Clean up apt cache to make container smaller -RUN apt-get clean +RUN sudo apt-get clean # Build -COPY . src/$FOLDER_NAME -RUN source /opt/ros/$ROS_DISTRO/setup.bash && colcon build +COPY --chown=cev . src/$FOLDER_NAME +RUN source /opt/ros/$ROS_DISTRO/setup.bash && colcon build --symlink-install -# Copy entrypoint script -COPY entrypoint.sh entrypoint.sh -RUN chmod +x entrypoint.sh -ENTRYPOINT ["/bin/bash", "/home/cev/entrypoint.sh"] +ENTRYPOINT ["/bin/bash", "/home/cev/src/rc-brain/scripts/deployed_entrypoint.sh"] diff --git a/Dockerfile.dev b/Dockerfile.dev index 795e722..833878c 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -3,44 +3,44 @@ FROM ros:humble SHELL ["/bin/bash", "-c"] +# Set up CEV user +RUN useradd -m -s /bin/bash cev +RUN usermod -aG sudo cev +# Grant passwordless sudo access to the 'sudo' group +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers +USER cev + ARG FOLDER_NAME=rc-brain ARG WORKSPACE_DIR=/home/cev WORKDIR $WORKSPACE_DIR +# Docker doesn't do this by default +RUN sudo chown -R cev /home/cev + # Source the ROS2 environment automatically when a new shell is created RUN echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc RUN echo "source $WORKSPACE_DIR/install/setup.bash" >> ~/.bashrc # Install dev tools -RUN apt-get update -RUN apt-get install -y curl -RUN curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc -RUN cat /etc/apt/trusted.gpg.d/apt.llvm.org.asc -RUN echo $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy main\n' \ - $'deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy main\n' \ - $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main\n' \ - $'deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main\n' \ - $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main\n' \ - $'deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main\n' \ - | tee -a /etc/apt/sources.list -RUN apt-get update -RUN apt-get install -y clang-format clang-format-19 +RUN sudo apt-get update +# Tools required to run clang install +RUN sudo apt-get install -y \ + curl \ + lsb-release \ + software-properties-common \ + gnupg +RUN curl -fsSL https://apt.llvm.org/llvm.sh | sudo bash -s -- 19 +RUN sudo apt-get install -y clangd-19 +RUN sudo update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-19 100 # Init rosdep and update package index -# for some reason we need to do this -RUN rm /var/lib/dpkg/info/libc-bin.* -RUN apt-get clean -RUN apt-get update -RUN apt-get install -y libc-bin RUN rosdep update --rosdistro $ROS_DISTRO -RUN mkdir -p src/$FOLDER_NAME - -# Copy install.sh -COPY --parents ./install.sh src/$FOLDER_NAME - -# Run install.sh -RUN src/$FOLDER_NAME/install.sh +# Run install_extra.sh, which installs additional dependencies +COPY --chown=cev scripts/install_extra.sh src/$FOLDER_NAME/scripts/ +RUN sudo src/$FOLDER_NAME/scripts/install_extra.sh -COPY --parents ./*/package.xml src/$FOLDER_NAME +# Install own dependencies +RUN mkdir -p src/$FOLDER_NAME +COPY --chown=cev --parents **/package.xml src/$FOLDER_NAME/ RUN source /opt/ros/$ROS_DISTRO/setup.bash && rosdep install --from-paths src -r -y diff --git a/README.md b/README.md index 4fc738c..740fe91 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,9 @@ # rc-brain One repo to rule them all -## Installation +## Installation (local) `git clone --recurse-submodules https://github.com/cornellev/rc-brain.git` -`cd rc-brain && sudo ./install.sh` - -## Docker -`docker build -t cev-rc-autonomy .` -Then run either -`docker run --rm ros2-autonomy launch` -or -`docker run --rm ros2-autonomy teleop` +`cd rc-brain && sudo ./scripts/install_extra.sh` ## Development @@ -21,12 +14,25 @@ Run `./scripts/deploy/arduino_deploy.sh` to compile and flash code to the arduin #### Formatting If you're not using the DevContainer, please make sure to install `clang-format`, ideally version 19! This will help keep the formatting of our C++ code formatted consistently. Python code is formatted by `black`. The DevContainer will install the `Black` extension for you, which ships with `black`, but otherwise you'll need to install it in VSCode or whatever other IDE you're using. +### Docker (DevContainer) +Everything should pretty much work out of the box, except... +> [!WARNING] +> You must have the Docker buildx plugin installed to build the image correctly. You may get very confusing failures! +> On Ubuntu, you can install the `docker-buildx-plugin` package. + ### Deployment #### Regular deployment If you're just developing, building Docker images will probably take too long. You can deploy normally just by running `scripts/deploy.sh`. It will drop you into an interactive session at the end so you can launch whatever you want to. #### Docker deployment +Use `docker build . --file Dockerfile.deploy -t rc-brain-deploy:latest` to build the deploy container. +Then run either `docker run --rm rc-brain-deploy launch` or `docker run --rm rc-brain-deploy teleop` to launch. + +> [!WARNING] +> You must have the Docker buildx plugin installed to build the image correctly. You may get very confusing failures! +> On Ubuntu, you can install the `docker-buildx-plugin` package. + Our Docker deployment pipeline works by pushing Docker images to a local registry and pulling them down on the mini cars. This avoids both long build times on the mini cars and the long time it takes to transfer whole images (as we can take advantage of Docker's cache). You'll have to do a bit of setup to get your computer to use the local registry. Note that you must be connected to `cev-router`. diff --git a/autonomy/launch/launch.py b/autonomy/launch/launch.py index 407b59d..a8fa4fa 100644 --- a/autonomy/launch/launch.py +++ b/autonomy/launch/launch.py @@ -5,21 +5,18 @@ from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource + def get_path(package, dir, file): - return os.path.join( - get_package_share_directory(package), - dir, - file - ) + return os.path.join(get_package_share_directory(package), dir, file) + def launch(package, file, launch_folder="launch", arguments={}): return IncludeLaunchDescription( - PythonLaunchDescriptionSource( - get_path(package, launch_folder, file) - ), - launch_arguments=arguments.items() + PythonLaunchDescriptionSource(get_path(package, launch_folder, file)), + launch_arguments=arguments.items(), ) + def generate_launch_description(): imu_config = get_path("autonomy", "config", "imu.yml") robot_localization_config = get_path("autonomy", "config", "robot_localization.yml") @@ -28,72 +25,95 @@ def generate_launch_description(): [ # STATIC TRANSFORMS Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_world_map', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_world_map", + output="screen", arguments=[ - '0', '0', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'world', - 'map' + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "world", + "map", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_map_odom', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_map_odom", + output="screen", arguments=[ - '0', '0', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'map', - 'odom' + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "map", + "odom", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_odom_base_link', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_odom_base_link", + output="screen", arguments=[ - '0', '0', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'odom', - 'base_link' + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "odom", + "base_link", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_base_link_lidar', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_base_link_lidar", + output="screen", arguments=[ - '-0.035', '-0.04', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '1', '0', # Rotation: M_PI - 'base_link', - 'laser' + "-0.035", + "-0.04", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "1", + "0", # Rotation: M_PI + "base_link", + "laser", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_base_link_imu', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_base_link_imu", + output="screen", arguments=[ - '-0.18', '0.0', '0', # Translation: x = -0.18, y = 0.07, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'base_link', - 'imu' + "-0.18", + "0.0", + "0", # Translation: x = -0.18, y = 0.07, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "base_link", + "imu", ], ), # # IMU Node( - package='witmotion_ros', - executable='witmotion_ros_node', - parameters=[ - imu_config - ] + package="witmotion_ros", + executable="witmotion_ros_node", + parameters=[imu_config], ), # Robot Localization # Node( @@ -119,21 +139,20 @@ def generate_launch_description(): "map_update_interval": 0.05, # "position_covariance_scale": 1.0, # "yaw_covariance_scale": 1.0, - "resolution": .09, - "min_laser_range": .15, + "resolution": 0.09, + "min_laser_range": 0.15, "max_laser_range": 12.0, "use_scan_matching": True, "do_loop_closing": True, "use_scan_barycenter": True, - "minimum_travel_distance": .05, - "minimum_travel_heading": .05, - "correlation_search_space_dimension": .2, + "minimum_travel_distance": 0.05, + "minimum_travel_heading": 0.05, + "correlation_search_space_dimension": 0.2, "loop_search_space_dimension": 3.0, - "angle_variance_penalty": 0.0 + "angle_variance_penalty": 0.0, } - ] + ], ), - # UNITREE LIDAR # Node( # package='unitree_lidar_ros2', @@ -153,11 +172,10 @@ def generate_launch_description(): # {'imu_frame': "unilidar_imu"}, # {'imu_topic': "unilidar/imu"}] # ), - # CEV Localization Node( package="cev_localization", - executable="ackermann_ekf", + executable="localization", name="cev_localization_node", output="screen", parameters=[ @@ -168,7 +186,7 @@ def generate_launch_description(): } ], ), - # Node( + # Node( # package="cev_planner_ros2", # executable="planner_node", # name="cev_planner_ros2_node", @@ -177,19 +195,15 @@ def generate_launch_description(): # # get_path("cev_planner_ros2", "config", "cev_planner.yaml") # # ], # ), - # Node( # package="cev_planner_ros2", # executable="planner_node", # name="cev_planner_ros2_node", # output="screen", # ), - # Trajectory Launch launch("trajectory_follower", "launch.py"), - ## LAUNCH FILES - # Teleop # launch("teleop", "launch.py"), # Autobrake @@ -197,7 +211,11 @@ def generate_launch_description(): # Serial Communicator launch("serial_com", "launch.py"), # RPLidar - launch("sllidar_ros2", "sllidar_a1_launch.py", arguments={"serial_port": "/dev/rplidar"}), + launch( + "sllidar_ros2", + "sllidar_a1_launch.py", + arguments={"serial_port": "/dev/rplidar"}, + ), # Encoder Odometry (Ackermann) launch("encoder_odometry", "launch.py"), ] diff --git a/autonomy/launch/mux.py b/autonomy/launch/mux.py index 2e279b5..6974f12 100644 --- a/autonomy/launch/mux.py +++ b/autonomy/launch/mux.py @@ -5,21 +5,18 @@ from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource + def get_path(package, dir, file): - return os.path.join( - get_package_share_directory(package), - dir, - file - ) + return os.path.join(get_package_share_directory(package), dir, file) + def launch(package, file, launch_folder="launch", arguments={}): return IncludeLaunchDescription( - PythonLaunchDescriptionSource( - get_path(package, launch_folder, file) - ), - launch_arguments=arguments.items() + PythonLaunchDescriptionSource(get_path(package, launch_folder, file)), + launch_arguments=arguments.items(), ) + def generate_launch_description(): imu_config = get_path("autonomy", "config", "imu.yml") robot_localization_config = get_path("autonomy", "config", "robot_localization.yml") @@ -28,72 +25,95 @@ def generate_launch_description(): [ # STATIC TRANSFORMS Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_world_map', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_world_map", + output="screen", arguments=[ - '0', '0', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'world', - 'map' + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "world", + "map", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_map_odom', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_map_odom", + output="screen", arguments=[ - '0', '0', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'map', - 'odom' + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "map", + "odom", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_odom_base_link', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_odom_base_link", + output="screen", arguments=[ - '0', '0', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'odom', - 'base_link' + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "odom", + "base_link", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_base_link_lidar', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_base_link_lidar", + output="screen", arguments=[ - '-0.035', '-0.04', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '1', '0', # Rotation: M_PI - 'base_link', - 'laser' + "-0.035", + "-0.04", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "1", + "0", # Rotation: M_PI + "base_link", + "laser", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_base_link_imu', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_base_link_imu", + output="screen", arguments=[ - '-0.18', '0.0', '0', # Translation: x = -0.18, y = 0.07, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'base_link', - 'imu' + "-0.18", + "0.0", + "0", # Translation: x = -0.18, y = 0.07, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "base_link", + "imu", ], ), # # IMU Node( - package='witmotion_ros', - executable='witmotion_ros_node', - parameters=[ - imu_config - ] + package="witmotion_ros", + executable="witmotion_ros_node", + parameters=[imu_config], ), # Robot Localization # Node( @@ -119,21 +139,20 @@ def generate_launch_description(): "map_update_interval": 0.05, # "position_covareiance_scale": 1.0, # "yaw_covariance_scale": 1.0, - "resolution": .09, - "min_laser_range": .15, + "resolution": 0.09, + "min_laser_range": 0.15, "max_laser_range": 12.0, "use_scan_matching": True, "do_loop_closing": True, "use_scan_barycenter": True, - "minimum_travel_distance": .05, - "minimum_travel_heading": .05, - "correlation_search_space_dimension": .2, + "minimum_travel_distance": 0.05, + "minimum_travel_heading": 0.05, + "correlation_search_space_dimension": 0.2, "loop_search_space_dimension": 3.0, - "angle_variance_penalty": 0.0 + "angle_variance_penalty": 0.0, } - ] + ], ), - # Node( # package="slam_toolbox", # executable="sync_slam_toolbox_node", @@ -165,7 +184,6 @@ def generate_launch_description(): # } # ] # ), - # UNITREE LIDAR # Node( # package='unitree_lidar_ros2', @@ -185,11 +203,10 @@ def generate_launch_description(): # {'imu_frame': "unilidar_imu"}, # {'imu_topic': "unilidar/imu"}] # ), - # CEV Localization Node( package="cev_localization", - executable="ackermann_ekf", + executable="localization", name="cev_localization_node", output="screen", parameters=[ @@ -200,7 +217,7 @@ def generate_launch_description(): } ], ), - # Node( + # Node( # package="cev_planner_ros2", # executable="planner_node", # name="cev_planner_ros2_node", @@ -209,21 +226,16 @@ def generate_launch_description(): # # get_path("cev_planner_ros2", "config", "cev_planner.yaml") # # ], # ), - # Node( # package="cev_planner_ros2", # executable="planner_node", # name="cev_planner_ros2_node", # output="screen", # ), - # Trajectory Launch # launch("trajectory_follower", "launch.py"), launch("mux", "launch.py"), - - ## LAUNCH FILES - # Teleop # launch("teleop", "launch.py"), # Autobrake @@ -231,7 +243,11 @@ def generate_launch_description(): # Serial Communicator launch("serial_com", "launch.py"), # RPLidar - launch("sllidar_ros2", "sllidar_a1_launch.py", arguments={"serial_port": "/dev/rplidar"}), + launch( + "sllidar_ros2", + "sllidar_a1_launch.py", + arguments={"serial_port": "/dev/rplidar"}, + ), # Encoder Odometry (Ackermann) launch("encoder_odometry", "launch.py"), ] diff --git a/autonomy/launch/teleop.py b/autonomy/launch/teleop.py index 1385bc3..e21294f 100644 --- a/autonomy/launch/teleop.py +++ b/autonomy/launch/teleop.py @@ -5,21 +5,18 @@ from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource + def get_path(package, dir, file): - return os.path.join( - get_package_share_directory(package), - dir, - file - ) + return os.path.join(get_package_share_directory(package), dir, file) + def launch(package, file, launch_folder="launch", arguments={}): return IncludeLaunchDescription( - PythonLaunchDescriptionSource( - get_path(package, launch_folder, file) - ), - launch_arguments=arguments.items() + PythonLaunchDescriptionSource(get_path(package, launch_folder, file)), + launch_arguments=arguments.items(), ) + def generate_launch_description(): imu_config = get_path("autonomy", "config", "imu.yml") robot_localization_config = get_path("autonomy", "config", "robot_localization.yml") @@ -28,73 +25,98 @@ def generate_launch_description(): [ # STATIC TRANSFORMS Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_world_map', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_world_map", + output="screen", arguments=[ - '0', '0', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'world', - 'map' + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "world", + "map", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_map_odom', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_map_odom", + output="screen", arguments=[ - '0', '0', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'map', - 'odom' + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "map", + "odom", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_odom_base_link', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_odom_base_link", + output="screen", arguments=[ - '0', '0', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'odom', - 'base_link' + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "odom", + "base_link", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_base_link_lidar', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_base_link_lidar", + output="screen", arguments=[ - '-0.035', '-0.04', '0', # Translation: x = 0.035, y = 0.04, z = 0 (meters) - '0', '0', '1', '0', # Rotation: M_PI - 'base_link', - 'laser' + "-0.035", + "-0.04", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "1", + "0", # Rotation: M_PI + "base_link", + "laser", ], ), Node( - package='tf2_ros', - executable='static_transform_publisher', - name='static_transform_publisher_base_link_imu', - output='screen', + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_base_link_imu", + output="screen", arguments=[ - '-0.18', '0.0', '0', # Translation: x = -0.18, y = 0.07, z = 0 (meters) - '0', '0', '0', '1', # Rotation: 0 - 'base_link', - 'imu' + "-0.18", + "0.0", + "0", # Translation: x = -0.18, y = 0.07, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "base_link", + "imu", ], ), # # IMU Node( - package='witmotion_ros', - executable='witmotion_ros_node', + package="witmotion_ros", + executable="witmotion_ros_node", parameters=[ # {"port": "witmotion"} imu_config - ] + ], ), # Robot Localization # Node( @@ -120,21 +142,20 @@ def generate_launch_description(): "map_update_interval": 0.05, # "position_covariance_scale": 1.0, # "yaw_covariance_scale": 1.0, - "resolution": .13, - "min_laser_range": .15, + "resolution": 0.13, + "min_laser_range": 0.15, "max_laser_range": 12.0, "use_scan_matching": True, "do_loop_closing": True, "use_scan_barycenter": True, - "minimum_travel_distance": .05, - "minimum_travel_heading": .05, - "correlation_search_space_dimension": .2, + "minimum_travel_distance": 0.05, + "minimum_travel_heading": 0.05, + "correlation_search_space_dimension": 0.2, "loop_search_space_dimension": 3.0, - "angle_variance_penalty": 0.0 + "angle_variance_penalty": 0.0, } - ] + ], ), - # UNITREE LIDAR # Node( # package='unitree_lidar_ros2', @@ -154,11 +175,10 @@ def generate_launch_description(): # {'imu_frame': "unilidar_imu"}, # {'imu_topic': "unilidar/imu"}] # ), - # CEV Localization Node( package="cev_localization", - executable="ackermann_ekf", + executable="localization", name="cev_localization_node", output="screen", parameters=[ @@ -169,20 +189,21 @@ def generate_launch_description(): } ], ), - # Trajectory Launch # launch("trajectory_follower", "launch.py"), - ## LAUNCH FILES - # Teleop - launch("teleop", "keyboard.py"), + launch("teleop", "launch.py"), # Autobrake # launch("autobrake", "launch.py"), # Serial Communicator launch("serial_com", "launch.py"), # RPLidar - launch("sllidar_ros2", "sllidar_a1_launch.py", arguments={"serial_port": "/dev/rplidar"}), + launch( + "sllidar_ros2", + "sllidar_a1_launch.py", + arguments={"serial_port": "/dev/rplidar"}, + ), # Encoder Odometry (Ackermann) launch("encoder_odometry", "launch.py"), ] diff --git a/cev/all/cev_autobrake_ros2/CMakeLists.txt b/cev/all/cev_autobrake_ros2/CMakeLists.txt new file mode 100644 index 0000000..8635046 --- /dev/null +++ b/cev/all/cev_autobrake_ros2/CMakeLists.txt @@ -0,0 +1,56 @@ + +cmake_minimum_required(VERSION 3.8) +project(autobrake) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(ackermann_msgs REQUIRED) +find_package(cev_msgs REQUIRED) +find_package(nav_msgs REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(tf2_geometry_msgs REQUIRED) + + +add_executable(autobrake_node src/autobrake_node.cpp) + +ament_target_dependencies(autobrake_node + rclcpp + std_msgs + sensor_msgs + ackermann_msgs + cev_msgs + tf2_ros + tf2_geometry_msgs) + +install(TARGETS autobrake_node + DESTINATION lib/${PROJECT_NAME}) + +install(DIRECTORY launch/ + DESTINATION share/${PROJECT_NAME}/launch +) + +install(DIRECTORY config/ + DESTINATION share/${PROJECT_NAME}/config +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() + diff --git a/mux/config/.gitkeep b/cev/all/cev_autobrake_ros2/config/.gitkeep similarity index 100% rename from mux/config/.gitkeep rename to cev/all/cev_autobrake_ros2/config/.gitkeep diff --git a/cev/all/cev_autobrake_ros2/launch/launch.py b/cev/all/cev_autobrake_ros2/launch/launch.py new file mode 100644 index 0000000..34b79f1 --- /dev/null +++ b/cev/all/cev_autobrake_ros2/launch/launch.py @@ -0,0 +1,19 @@ +from launch import LaunchDescription +from launch_ros.actions import Node +from ament_index_python.packages import get_package_share_directory +import os +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource + + +def generate_launch_description(): + return LaunchDescription( + [ + Node( + package="autobrake", + executable="autobrake_node", + name="autobrake_node", + ) + ] + ) + diff --git a/cev/all/cev_autobrake_ros2/package.xml b/cev/all/cev_autobrake_ros2/package.xml new file mode 100644 index 0000000..2641a8c --- /dev/null +++ b/cev/all/cev_autobrake_ros2/package.xml @@ -0,0 +1,28 @@ + + + + autobrake + 0.0.0 + TODO: Package description + sloth + TODO: License declaration + + ament_cmake + + ament_lint_auto + ament_lint_common + + rclcpp + std_msgs + sensor_msgs + ackermann_msgs + cev_msgs + nav_msgs + tf2 + tf2_ros + tf2_geometry_msgs + + + ament_cmake + + diff --git a/cev/all/cev_autobrake_ros2/src/autobrake_node.cpp b/cev/all/cev_autobrake_ros2/src/autobrake_node.cpp new file mode 100644 index 0000000..10bed6f --- /dev/null +++ b/cev/all/cev_autobrake_ros2/src/autobrake_node.cpp @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include "cev_msgs/msg/sensor_collect.hpp" +#include + +using std::placeholders::_1; + +/** NOTES + * R = L / tan(steering_angle) + * Circle Center = (-R, 0) + * + * Obstacle = (rsin(theta), rcos(theta)) + * + * Angle from center to obstacle = tan(O_y / (O_x + R)) + * Dist from center to obstacle = sqrt((O_x + R)^2 + O_y^2) + * + * Circum dist to obstacle = R * angle from center to obstacle + * Time to hit obstacle = circum dist to obstacle / velocity + * + * NOTE: LIDAR'S 0 is forward, and angles increment clockwise + */ + +class AutobrakeNode : public rclcpp::Node { +public: + AutobrakeNode(): Node("autobrake") { + forward_brake_.data = MAX_VEL; + + scan_sub_ = this->create_subscription("scan", 1, + std::bind(&AutobrakeNode::checkCollision, this, _1)); + + sensor_collect_sub_ = this->create_subscription( + "sensor_collect", 1, std::bind(&AutobrakeNode::setVars, this, _1)); + + rc_movement_sub_ = this->create_subscription( + "rc_movement_msg", 1, std::bind(&AutobrakeNode::setTargets, this, _1)); + + forward_brake_pub_ = this->create_publisher("auto_max_vel", 1); + + timer_ = this->create_wall_timer(std::chrono::milliseconds(10), + std::bind(&AutobrakeNode::publishBrake, this)); + + RCLCPP_INFO(this->get_logger(), "Autobrake node initialized."); + } + +private: // TODO: Make these constants configurable and use transforms instead of direct lidar + const float VEHICLE_LENGTH = 0.185; + const float VEHICLE_WIDTH = 0.18; + const float AUTOBRAKE_TIME = 0.7; + const float AUTOBRAKE_DISTANCE = 0.2; + const float MAX_VEL = 1.5; + const int MIN_COLLISIONS_FOR_BRAKE = 3; + const float LIDAR_ROTATIONAL_OFFSET = M_PI; + const float LIDAR_HORIZONTAL_OFFSET = 0.035; + + float steering_angle_ = 0; + float velocity_ = 0; + float target_velocity_ = 0; + std_msgs::msg::Float32 forward_brake_; + + rclcpp::Subscription::SharedPtr scan_sub_; + rclcpp::Subscription::SharedPtr sensor_collect_sub_; + rclcpp::Subscription::SharedPtr rc_movement_sub_; + rclcpp::Publisher::SharedPtr forward_brake_pub_; + rclcpp::TimerBase::SharedPtr timer_; + + // Calculate the maximum velocity based on the distance to an obstacle + float maxVelocity(float dist) { + if (dist < 0) return MAX_VEL; + if (dist < AUTOBRAKE_DISTANCE) return 0; + + return std::max(0.0f, -3.5f + std::sqrt(49 + 40 * dist) / 2 - 0.2f); + } + + // LIDAR'S 0 is forward, and angles increment clockwise + // Check for potential collisions based on laser scan data + void checkCollision(const sensor_msgs::msg::LaserScan::SharedPtr data) { + int invert_flag = 1; + float min_vel = MAX_VEL; + + if (steering_angle_ < 0) invert_flag = -1; + + // Calculate turning radius (L / tan(steering_angle)), or infinity if driving straight + float turning_radius = std::abs(steering_angle_) > 0.01 + ? VEHICLE_LENGTH / std::tan(invert_flag * steering_angle_) + : std::numeric_limits::infinity(); + + // Calculate turning radii of inside and outside wheels + float inner_wheel_radius = turning_radius - VEHICLE_WIDTH / 2; + float outer_wheel_radius = turning_radius + VEHICLE_WIDTH / 2; + + for (size_t i = 0; i < data->ranges.size(); ++i) { + if (data->ranges[i] < data->range_min || data->ranges[i] > data->range_max) continue; + + float dist_to_obstacle = std::numeric_limits::infinity(); + + // X and Y coordinates of obstacle relative to vehicle + float theta = LIDAR_ROTATIONAL_OFFSET + data->angle_min + i * data->angle_increment; + float r = data->ranges[i]; + float x = invert_flag * (r * std::sin(theta) + LIDAR_HORIZONTAL_OFFSET); + float y = r * std::cos(theta); + + if (turning_radius == std::numeric_limits::infinity()) { + // If driving straight, check if obstacle is within the width of the vehicle + if (std::abs(x) < VEHICLE_WIDTH / 2) + dist_to_obstacle = y; + else + continue; + } else { + // For curving, calculate radius distance to obstacle from center of movement circle + float radius_dist_to_obstacle = + std::sqrt((x + turning_radius) * (x + turning_radius) + y * y); + + if (outer_wheel_radius > radius_dist_to_obstacle + && radius_dist_to_obstacle > inner_wheel_radius) { + // Calculate angle from center of movement circle to obstacle + float angle_from_center_to_obstacle = std::atan2(y, turning_radius + x); + if (angle_from_center_to_obstacle < 0) + angle_from_center_to_obstacle += 2 * M_PI; + angle_from_center_to_obstacle = fmod(angle_from_center_to_obstacle, 2 * M_PI); + + // Calculate circumferential distance to obstacle + dist_to_obstacle = turning_radius * angle_from_center_to_obstacle; + } else + continue; + } + + // If the obstacle is within a valid distance, adjust the minimum velocity + if (dist_to_obstacle >= 0) min_vel = std::min(min_vel, maxVelocity(dist_to_obstacle)); + } + + // Set the brake value to the minimum calculated velocity + forward_brake_.data = min_vel; + } + + // Callback to set the velocity and steering angle from the sensor_collect topic + void setVars(const cev_msgs::msg::SensorCollect::SharedPtr data) { + velocity_ = data->velocity; + steering_angle_ = -data->steering_angle; // Invert the steering angle + } + + // Callback to set the target velocity from the AckermannDrive topic + void setTargets(const ackermann_msgs::msg::AckermannDrive::SharedPtr data) { + target_velocity_ = data->speed; + } + + // Publish the brake value at a regular interval + void publishBrake() { + forward_brake_pub_->publish(forward_brake_); + } +}; + +int main(int argc, char* argv[]) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/cev/all/cev_encoder_odometry_ros2/CMakeLists.txt b/cev/all/cev_encoder_odometry_ros2/CMakeLists.txt new file mode 100644 index 0000000..7c02eaf --- /dev/null +++ b/cev/all/cev_encoder_odometry_ros2/CMakeLists.txt @@ -0,0 +1,46 @@ + +cmake_minimum_required(VERSION 3.8) +project(encoder_odometry) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) +find_package(cev_msgs REQUIRED) +find_package(nav_msgs REQUIRED) +find_package(tf2_ros REQUIRED) + +add_executable(odometry src/odometry.cpp) +ament_target_dependencies(odometry rclcpp std_msgs cev_msgs nav_msgs tf2_ros) + +install(TARGETS + odometry + DESTINATION lib/${PROJECT_NAME} +) + +install(DIRECTORY launch/ + DESTINATION share/${PROJECT_NAME}/launch +) + +install(DIRECTORY config/ + DESTINATION share/${PROJECT_NAME}/config +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() + diff --git a/serial_com/config/.gitkeep b/cev/all/cev_encoder_odometry_ros2/config/.gitkeep similarity index 100% rename from serial_com/config/.gitkeep rename to cev/all/cev_encoder_odometry_ros2/config/.gitkeep diff --git a/cev/all/cev_encoder_odometry_ros2/launch/launch.py b/cev/all/cev_encoder_odometry_ros2/launch/launch.py new file mode 100644 index 0000000..d6c5ba7 --- /dev/null +++ b/cev/all/cev_encoder_odometry_ros2/launch/launch.py @@ -0,0 +1,20 @@ + +from launch import LaunchDescription +from launch_ros.actions import Node +from ament_index_python.packages import get_package_share_directory +import os +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource + + +def generate_launch_description(): + return LaunchDescription( + [ + Node( + package="encoder_odometry", + executable="odometry", + name="encoder_odometry_node", + ) + ] + ) + diff --git a/cev/all/cev_encoder_odometry_ros2/package.xml b/cev/all/cev_encoder_odometry_ros2/package.xml new file mode 100644 index 0000000..f7e7e70 --- /dev/null +++ b/cev/all/cev_encoder_odometry_ros2/package.xml @@ -0,0 +1,24 @@ + + + + encoder_odometry + 0.0.0 + TODO: Package description + sloth + TODO: License declaration + + ament_cmake + + ament_lint_auto + ament_lint_common + + rclcpp + std_msgs + cev_msgs + nav_msgs + tf2_ros + + + ament_cmake + + diff --git a/cev/all/cev_encoder_odometry_ros2/src/odometry.cpp b/cev/all/cev_encoder_odometry_ros2/src/odometry.cpp new file mode 100644 index 0000000..e1c6002 --- /dev/null +++ b/cev/all/cev_encoder_odometry_ros2/src/odometry.cpp @@ -0,0 +1,122 @@ +#include +#include +#include "cev_msgs/msg/sensor_collect.hpp" +#include +#include +#include +#include + +using std::placeholders::_1; + +class OdometryNode : public rclcpp::Node +{ +public: + OdometryNode() : Node("encoder_odometry") { + RCLCPP_INFO(this->get_logger(), "Initializing Ackermann Odometry Node"); + + sensor_collect_sub_ = this->create_subscription( + "sensor_collect", 1, std::bind(&OdometryNode::updateOdometry, this, _1)); + + odom_publisher_ = this->create_publisher("/encoder_odometry", 1); + } + +private: + const float WHEELBASE = 0.185; + + double x_ = 0.0; // Forward of base_link is x + double y_ = 0.0; + + double yaw_ = 0.0; + + double old_left_rotations_ = 0.0; + double new_left_rotations_ = 0.0; + + double old_steering_angle_ = 0.0; + + rclcpp::Time prev_time_; + + bool initialized = false; + + double covariance_[36] = { + 0.01, 0, 0, 0, 0, 0, + 0, 0.01, 0, 0, 0, 0, + 0, 0, 0.01, 0, 0, 0, + 0, 0, 0, 0.01, 0, 0, + 0, 0, 0, 0, 0.01, 0, + 0, 0, 0, 0, 0, 0.01 + }; + + rclcpp::Subscription::SharedPtr sensor_collect_sub_; + rclcpp::Publisher::SharedPtr odom_publisher_; + + void updateOdometry(const cev_msgs::msg::SensorCollect::SharedPtr data) { + if (!initialized) { + prev_time_ = this->now(); + initialized = true; + return; + } + + auto current_time = this->now(); + double dt = (current_time - prev_time_).seconds(); + if (dt < 1e-6) return; + + double delta_distance = data->velocity * dt; + + double new_steering_angle = data->steering_angle; + double average_steering_angle = new_steering_angle; + // double average_steering_angle = (new_steering_angle + old_steering_angle_) / 2.0; + + double turning_radius = WHEELBASE / std::tan(average_steering_angle); + + double delta_theta = delta_distance / turning_radius; + + yaw_ += delta_theta; + x_ += delta_distance * std::cos(yaw_); + y_ += delta_distance * std::sin(yaw_); + + publishOdometry(this->now(), data->velocity, delta_theta / dt); + + // Update carry vars + old_steering_angle_ = new_steering_angle; + prev_time_ = current_time; + } + + void publishOdometry(const rclcpp::Time ¤t_time, double linear_velocity, double angular_velocity) + { + /** + * Publish odometry message using current pose/twist along with calculated velocities. + */ + auto odom_msg = nav_msgs::msg::Odometry(); + odom_msg.header.stamp = current_time; + odom_msg.header.frame_id = "odom"; + odom_msg.child_frame_id = "base_link"; + + odom_msg.pose.pose.position.x = x_; + odom_msg.pose.pose.position.y = y_; + + tf2::Quaternion q; + q.setRPY(0.0, 0.0, yaw_); + odom_msg.pose.pose.orientation.x = q.x(); + odom_msg.pose.pose.orientation.y = q.y(); + odom_msg.pose.pose.orientation.z = q.z(); + odom_msg.pose.pose.orientation.w = q.w(); + + odom_msg.twist.twist.linear.x = linear_velocity; + odom_msg.twist.twist.angular.z = angular_velocity; + + for (int i = 0; i < 36; i++) + { + odom_msg.pose.covariance[i] = covariance_[i]; + } + + odom_publisher_->publish(odom_msg); + } +}; + +int main(int argc, char *argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/cev/all/cev_localization_ros2/.clang-format b/cev/all/cev_localization_ros2/.clang-format new file mode 100644 index 0000000..1d9b175 --- /dev/null +++ b/cev/all/cev_localization_ros2/.clang-format @@ -0,0 +1,36 @@ +# CEV C++ setup +--- +BasedOnStyle: Google +IndentWidth: 4 +Language: Cpp +UseTab: Never +DerivePointerAlignment: false +PointerAlignment: Left +BreakStringLiterals: true +ReflowComments: true +ContinuationIndentWidth: 4 +ColumnLimit: 100 +AlignAfterOpenBracket: DontAlign +AlignTrailingComments: + Kind: Always + OverEmptyLines: 2 +AlignOperands: Align +BreakBeforeBinaryOperators: NonAssignment +PenaltyBreakAssignment: 60 +PenaltyBreakOpenParenthesis: 50 +IndentCaseLabels: true +AllowShortFunctionsOnASingleLine: Empty +SortIncludes: false +MacroBlockBegin: "^#if .*$" +MacroBlockEnd: "^#endif$" +AlignEscapedNewlines: Right +IndentPPDirectives: BeforeHash +IndentAccessModifiers: false +AccessModifierOffset: -4 +SpaceBeforeRangeBasedForLoopColon: false +SpaceBeforeCaseColon: false +SpaceBeforeCtorInitializerColon: false +SpaceAfterTemplateKeyword: false +NamespaceIndentation: All +FixNamespaceComments: false +--- diff --git a/cev/all/cev_localization_ros2/.devcontainer/Dockerfile.dev b/cev/all/cev_localization_ros2/.devcontainer/Dockerfile.dev new file mode 100644 index 0000000..626dbf7 --- /dev/null +++ b/cev/all/cev_localization_ros2/.devcontainer/Dockerfile.dev @@ -0,0 +1,77 @@ +# Start with the official ROS Humble ROS-Core image based on Ubuntu Jammy +FROM ros:humble-ros-core-jammy + +SHELL ["/bin/bash", "-c"] + +# Install basic build tools and ROS2 development dependencies +RUN apt-get update && apt-get install --no-install-recommends -y \ + build-essential \ + git \ + python3-colcon-common-extensions \ + python3-colcon-mixin \ + python3-rosdep \ + python3-vcstool \ + && rm -rf /var/lib/apt/lists/* + +# Set the ROS distro to Humble +ENV ROS_DISTRO=humble + +# Initialize rosdep and update for the current ROS distro +RUN rosdep init && \ + rosdep update --rosdistro $ROS_DISTRO + +# Add and update colcon mixins and metadata +RUN colcon mixin add default \ + https://raw.githubusercontent.com/colcon/colcon-mixin-repository/master/index.yaml && \ + colcon mixin update && \ + colcon metadata add default \ + https://raw.githubusercontent.com/colcon/colcon-metadata-repository/master/index.yaml && \ + colcon metadata update + +# Install ROS2 base packages specific to Humble distribution +RUN apt-get update && apt-get install -y --no-install-recommends \ + ros-humble-ros-base=0.10.0-1* \ + && rm -rf /var/lib/apt/lists/* + +# Set environment variables and source ROS2 Humble setup +ENV LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 + +# Source the ROS2 environment automatically when a new shell is created +RUN echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc +RUN echo "source /root/ws/install/setup.bash" >> ~/.bashrc + +RUN mkdir -p /root/ws/src +RUN mkdir -p /root/ws/tmp +COPY . /root/ws/tmp + +WORKDIR /root/ws + +RUN sudo apt update + +# Install dev tools +RUN apt-get update +RUN apt-get install -y curl +RUN curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc +RUN cat /etc/apt/trusted.gpg.d/apt.llvm.org.asc +RUN echo $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy main\n' \ + $'deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy main\n' \ + $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main\n' \ + $'deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main\n' \ + | tee -a /etc/apt/sources.list +RUN apt-get update +RUN apt-get install -y clang-format clang-format-19 + +RUN source /opt/ros/$ROS_DISTRO/setup.bash \ + && cd /root/ws \ + && rosdep update \ + && rosdep install --from-paths tmp -r -y + +RUN rm -rf /root/ws/tmp + +RUN source /opt/ros/$ROS_DISTRO/setup.bash \ + && cd /root/ws \ + && colcon build \ + && git clone https://github.com/cornellev/cev_msgs.git src/cev_msgs + +EXPOSE 4567 \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/.devcontainer/devcontainer.json b/cev/all/cev_localization_ros2/.devcontainer/devcontainer.json new file mode 100644 index 0000000..bc954aa --- /dev/null +++ b/cev/all/cev_localization_ros2/.devcontainer/devcontainer.json @@ -0,0 +1,51 @@ +{ + "name": "KF Dev-Container", + "build": { + "dockerfile": "Dockerfile.dev" + }, + "workspaceMount": "source=${localWorkspaceFolder},target=/root/ws/src/ackermann_kf,type=bind", + "workspaceFolder": "/root/ws/src/ackermann_kf", + "runArgs": [ + "--rm", + "--network=host", + "--ipc=host", + "--privileged" + ], + "containerEnv": { + "DISPLAY": "${localEnv:DISPLAY}" + }, + "mounts": [ + "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached" + ], + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/bin/bash" + } + } + }, + "[cpp]": { + "editor.defaultFormatter": "xaver.clang-format" + }, + "[python]": { + "diffEditor.ignoreTrimWhitespace": false, + "gitlens.codeLens.symbolScopes": [ + "!Module" + ], + "editor.formatOnType": true, + "editor.wordBasedSuggestions": "off", + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "extensions": [ + "ms-iot.vscode-ros", + "ms-vscode.cpptools-extension-pack", + "ms-python.black-formatter", + "xaver.clang-format" + ] + } + }, + "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}" +} \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/.gitignore b/cev/all/cev_localization_ros2/.gitignore new file mode 100644 index 0000000..463fcb4 --- /dev/null +++ b/cev/all/cev_localization_ros2/.gitignore @@ -0,0 +1,6 @@ +.idea/ +build/ +log/ +install/ +cmake-build-debug/ +.vscode/ \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/CMakeLists.txt b/cev/all/cev_localization_ros2/CMakeLists.txt new file mode 100644 index 0000000..c3d2239 --- /dev/null +++ b/cev/all/cev_localization_ros2/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.10) +project(cev_localization LANGUAGES CXX) + +# Set the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Find dependencies +find_package(ament_cmake REQUIRED) +find_package(Eigen3 REQUIRED) +find_package(rclcpp REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(tf2 REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(tf2_geometry_msgs REQUIRED) +find_package(nav_msgs REQUIRED) +find_package(yaml-cpp REQUIRED) + +# CEV +find_package(cev_msgs REQUIRED) + +# Add subdirectory for the library +add_subdirectory(cev_kalman_filter) + +# Include directories for project headers +include_directories( + include + cev_kalman_filter/include +) + +# Source files in the main src folder +set(SOURCES + src/localization.cpp + src/config_parser.cpp + src/ros_sensor.cpp + src/std_ros_sensors.cpp +) + +# Declare the executable +add_executable(localization ${SOURCES}) + +# Link libraries +ament_target_dependencies(localization + rclcpp + sensor_msgs + geometry_msgs + tf2 + tf2_ros + tf2_geometry_msgs + nav_msgs + cev_msgs + Eigen3 +) + +target_link_libraries(localization cev_kalman_filter yaml-cpp) + +# Install targets +install(TARGETS localization + DESTINATION lib/${PROJECT_NAME}) + +# Install additional resources +install(DIRECTORY launch config + DESTINATION share/${PROJECT_NAME}) + +install(DIRECTORY rosbag + DESTINATION share/${PROJECT_NAME}) + +# Ament package configuration +ament_package() diff --git a/cev/all/cev_localization_ros2/README.md b/cev/all/cev_localization_ros2/README.md new file mode 100644 index 0000000..9e4ef26 --- /dev/null +++ b/cev/all/cev_localization_ros2/README.md @@ -0,0 +1,2 @@ +# kalman_odom +ROS 2 package for Ackermann Model EKF-based localization (in development) diff --git a/cev/all/cev_localization_ros2/TODO.md b/cev/all/cev_localization_ros2/TODO.md new file mode 100644 index 0000000..5e3ae8a --- /dev/null +++ b/cev/all/cev_localization_ros2/TODO.md @@ -0,0 +1,5 @@ + - Use message covariance, allow constant override in config + - Incorporate steering angle in ackermann informed calcs + - Write a proper README + - Figure out how to make an official ROS2 open source project + - Make sure the pipelining update frequencies make sense... (should we only update the roots periodically?) \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/cev_kalman_filter b/cev/all/cev_localization_ros2/cev_kalman_filter new file mode 160000 index 0000000..70bff5f --- /dev/null +++ b/cev/all/cev_localization_ros2/cev_kalman_filter @@ -0,0 +1 @@ +Subproject commit 70bff5f8d6eb21fd585c5109e0ea74a4a2211a8f diff --git a/cev/all/cev_localization_ros2/config/.gitkeep b/cev/all/cev_localization_ros2/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cev/all/cev_localization_ros2/config/conf.rviz b/cev/all/cev_localization_ros2/config/conf.rviz new file mode 100644 index 0000000..ad6ca68 --- /dev/null +++ b/cev/all/cev_localization_ros2/config/conf.rviz @@ -0,0 +1,282 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /TF1/Frames1 + - /MeowOdom1 + - /MeowOdom1/Shape1 + - /FilterOdom1 + - /FilterOdom1/Shape1 + Splitter Ratio: 0.5 + Tree Height: 909 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Goal Pose1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 + - Class: rviz_common/Time + Experimental: false + Name: Time + SyncMode: 0 + SyncSource: "" +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: false + map: + Value: false + meow_link: + Value: true + odom: + Value: true + world: + Value: false + Marker Scale: 1 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: false + Tree: + world: + map: + odom: + meow_link: + {} + Update Interval: 0 + Value: true + - Angle Tolerance: 0.10000000149011612 + Class: rviz_default_plugins/Odometry + Covariance: + Orientation: + Alpha: 0.5 + Color: 255; 255; 127 + Color Style: Unique + Frame: Local + Offset: 1 + Scale: 1 + Value: true + Position: + Alpha: 0.30000001192092896 + Color: 204; 51; 204 + Scale: 1 + Value: true + Value: false + Enabled: true + Keep: 100 + Name: MeowOdom + Position Tolerance: 0.10000000149011612 + Shape: + Alpha: 1 + Axes Length: 1 + Axes Radius: 0.10000000149011612 + Color: 170; 0; 255 + Head Length: 0.20000000298023224 + Head Radius: 0.10000000149011612 + Shaft Length: 0 + Shaft Radius: 0.05000000074505806 + Value: Arrow + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /odometry/meow + Value: true + - Angle Tolerance: 0.10000000149011612 + Class: rviz_default_plugins/Odometry + Covariance: + Orientation: + Alpha: 0.5 + Color: 255; 255; 127 + Color Style: Unique + Frame: Local + Offset: 1 + Scale: 1 + Value: true + Position: + Alpha: 0.30000001192092896 + Color: 204; 51; 204 + Scale: 1 + Value: true + Value: false + Enabled: true + Keep: 100 + Name: FilterOdom + Position Tolerance: 0.10000000149011612 + Shape: + Alpha: 1 + Axes Length: 1 + Axes Radius: 0.10000000149011612 + Color: 170; 0; 0 + Head Length: 0.20000000298023224 + Head Radius: 0.10000000149011612 + Shaft Length: 0 + Shaft Radius: 0.05000000074505806 + Value: Arrow + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /odometry/filtered + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: map + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 15.735194206237793 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.5603981018066406 + Target Frame: + Value: Orbit (rviz) + Yaw: 0.7553980350494385 + Saved: + - Class: rviz_default_plugins/Orbit + Distance: 15.735194206237793 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Orbit + Near Clip Distance: 0.009999999776482582 + Pitch: 0.5603981018066406 + Target Frame: + Value: Orbit (rviz) + Yaw: 0.7553980350494385 + - Class: rviz_default_plugins/Orbit + Distance: 15.735194206237793 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Orbit + Near Clip Distance: 0.009999999776482582 + Pitch: 0.5603981018066406 + Target Frame: + Value: Orbit (rviz) + Yaw: 0.7553980350494385 +Window Geometry: + Displays: + collapsed: false + Height: 1200 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd00000004000000000000015600000416fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b00000416000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010000000416fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003b00000416000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000007800000003efc0100000002fb0000000800540069006d00650100000000000007800000025300fffffffb0000000800540069006d006501000000000000045000000000000000000000051e0000041600000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Time: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1920 + X: 0 + Y: 0 diff --git a/cev/all/cev_localization_ros2/config/ekf.conf_doc b/cev/all/cev_localization_ros2/config/ekf.conf_doc new file mode 100644 index 0000000..4b3f697 --- /dev/null +++ b/cev/all/cev_localization_ros2/config/ekf.conf_doc @@ -0,0 +1,37 @@ +# General settings for the Kalman Filter node output +odometry_settings: + time_step: double # Time step for the filter publish (in seconds) + topic: str # Topic to publish the odometry data + +# Define the sensors used by the node +sensors: + sensor_name: + type: sensor_type # Sensor type (IMU, GPS, etc.) + topic: str # Topic to subscribe to + frame_id: str # Frame ID for the sensor data + + state: [ + x, dx, d2x, + y, dy, d2y, + z, dz, d2z, + roll, droll, d2roll, + pitch, dpitch, d2pitch, + yaw, dyaw, d2yaw, + tau, dtau, d2tau + ] # State variables for the sensor. Each should be true or false + covariance: [double] # Covariance matrix for the sensor data + estimator_models: [ + model_name_1, + model_name_2, + ... + ] # List of strings representing models to use for the sensor data by name + +# Define update models with their parameters +update_models: + model_name: + type: model_type # Model type (ACKERMANN, CARTESIAN, etc.) + parameters: [ + parameter_1, + parameter_2, + ... + ] # Parameters for the model diff --git a/cev/all/cev_localization_ros2/config/ekf_real.yml b/cev/all/cev_localization_ros2/config/ekf_real.yml new file mode 100644 index 0000000..b22943a --- /dev/null +++ b/cev/all/cev_localization_ros2/config/ekf_real.yml @@ -0,0 +1,50 @@ +# Define the sensors used by the node +sensors: + witmotion_imu: + type: "IMU" # Sensor type (IMU, GPS, etc.) + topic: "/imu" # Topic to subscribe to + frame_id: "imu_frame" # Frame ID for the sensor data + + state: ["d2_x", "d2_y", "yaw"] # State variables to use from the sensor data + covariance_multiplier: 1.0 # Multiplier to multiply message covariance by + use_message_covariance: true + estimator_models: ["ackermann", "cartesian"] # List of strings representing models to use for the sensor data by name + + encoder_odometry: + type: "RAW" + topic: "/sensor_collect" + frame_id: "odom_frame" + + state: ["d_x", "tau"] + covariance_multiplier: 2.0 + estimator_models: ["ackermann", "cartesian"] + +# Define update models with their parameters +update_models: + ackermann: + type: "ACKERMANN" # Model type (ACKERMANN, CARTESIAN, etc.) + state: ["x", "y", "d_x", "d_y", "yaw", "tau"] # State variables to use from the model in updates + estimator_models: [] + + publish: + active: true + topic: "/odometry/ackermann" + child_frame: "base_link" + parent_frame: "odom" + rate: 100 + + publish_tf: true + + cartesian: + type: "CARTESIAN" + state: ["x", "y", "d_x", "d_y", "yaw", "d_yaw"] + estimator_models: ["ackermann"] + + publish: + active: false + topic: "/odometry/cartesian" + child_frame: "base_link" + parent_frame: "odom_cart" + rate: 200 + + publish_tf: false diff --git a/cev/all/cev_localization_ros2/ekf_sim.py b/cev/all/cev_localization_ros2/ekf_sim.py new file mode 100644 index 0000000..bc9cc86 --- /dev/null +++ b/cev/all/cev_localization_ros2/ekf_sim.py @@ -0,0 +1,278 @@ +import copy + +import numpy as np +import matplotlib.pyplot as plt +import random +import math + +""" +Simple KF for 2D robot localization + +Notes: + x, y, yaw are relative to the parent frame. + dx, dy, ddx, ddy, steering_angle are relative to the robot frame, where x is forward +""" + +cycle_dt = .01 # Constant time step +real_t = 10 # Time to run the simulation for + +WB = .3 + +# Current state +x = np.array([ + 0., # x + 0., # y + .5, # x' + 0., # y' + 0., # yaw + .79, # steering angle +]) + +# State covariance +P = np.eye(len(x)) * .1 + + +# Simple bicycle model update step +def f(x, dt): + x_ = x[0] + y_ = x[1] + d_x = x[2] + d_y = x[3] + yaw = x[4] + tau = x[5] + + d_yaw = d_x * np.sin(tau) / WB * dt + yaw_new = yaw + d_yaw + + x_new = x_ + d_x * np.cos(yaw + tau) * dt + y_new = y_ + d_x * np.sin(yaw + tau) * dt + + return np.array([x_new, y_new, d_x, d_y, yaw_new, tau]) + + +def F(x, dt): + # Generates a Jacobian matrix representing an Simple bicycle model update step + + d_x = x[2] + yaw = x[4] + tau = x[5] + + p_yaw_dx = dt * np.sin(tau) / WB + p_yaw_tau = dt * d_x * np.cos(tau) / WB + + p_x_dx = dt * np.cos(yaw + tau) + p_x_yaw = -dt * d_x * np.sin(yaw + tau) + p_x_tau = -dt * d_x * np.sin(yaw + tau) + + p_y_dx = dt * np.sin(yaw + tau) + p_y_yaw = dt * d_x * np.cos(yaw + tau) + p_y_tau = dt * d_x * np.cos(yaw + tau) + + # Jacobian matrix representing an Ackermann update step + return np.array([ + # x y dx dy yaw tau # OUTPUT + [1., 0., p_x_dx, 0., p_x_yaw, p_x_tau], # x + [0., 1., p_y_dx, 0., p_y_yaw, p_y_tau], # y + [0., 0., 1., 0., 0., 0.], # dx + [0., 0., 0., 1., 0., 0.], # dy + [0., 0., p_yaw_dx, 0., 1., p_yaw_tau], # yaw + [0., 0., 0., 0., 0., 1.], # tau + ]) + + +# Measurement jacobian matrix +def H(x, dt, tr): + # `tr` is the transformation from the state space to the sensor space + return (tr @ F(x, dt).T).T + + +# Process noise covariance (update step). Low Q value means high confidence in model +def Q(dt): + return dt * np.eye(len(x)) * .1 + + +def predict(x, P, dt): + # P = current variance + # x = current state + + F_k = F(x, dt) + Q_k = Q(dt) + + # Generate new state prediction with prediction matrix + state = f(x, dt) + + # Generate new covariance prediction, with additional covariance to account for process noise + cov = F_k @ P @ F_k.T + Q_k + + return state, cov + + +def sensor_update( + state_, + variance, + sensor_state, + sensor_mul_matrix, + sensor_variance, + dt, + update_time_elapsed +): + # H_k = H(state_, update_time_elapsed, sensor_mul_matrix) + H_k = H(state_, update_time_elapsed, sensor_mul_matrix) + R_k = sensor_variance + h = sensor_mul_matrix + + predicted_state, predicted_variance = predict(state_, variance, dt) + + predicted_sensor = h @ predicted_state + real_sensor = sensor_state + + y_k = real_sensor - predicted_sensor + + S_k = H_k @ predicted_variance @ H_k.T + R_k + K_k = predicted_variance @ H_k.T @ np.linalg.inv(S_k) + + state_ = predicted_state + K_k @ y_k + predicted_variance = (np.eye(len(x)) - K_k @ H_k) @ predicted_variance + + return state_, predicted_variance + + +def fake_imu(state): + return np.array([ + 0., + 0., + state[2] + random.gauss(0, .01), + state[3] + random.gauss(0, .01), + state[4] + random.gauss(0, .1), + 0. + ]) + + +def fake_enc(state): + return np.array([ + 0., + 0., + state[2] + random.gauss(0, .1), + 0., + 0., + state[5] + ]) + +rseed = 1000 +random.seed(rseed) + +def root_means_squared_estimation_error(ground_truth_states, estimated_states): + """ + Take an array of ground truth and estimated states. Return the mean squared error of x and y + """ + x_error = 0 + y_error = 0 + + for i in range(len(ground_truth_states)): + x_error += (ground_truth_states[i][0] - estimated_states[i][0]) ** 2 + y_error += (ground_truth_states[i][1] - estimated_states[i][1]) ** 2 + + return (x_error / len(ground_truth_states) + y_error / len(ground_truth_states)) ** .5 + + +def main_loop(): + state = copy.deepcopy(x) + variance = copy.deepcopy(P) + next_state = copy.deepcopy(x) + next_var = copy.deepcopy(P) + + true_states = [] + est_states = [] + + sensor_mul_matrix = { + "imu": np.array([ + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0] + ]), + "enc": np.array([ + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1] + ]), + } + + last_update_time_matrix = { + "imu": 0, + "enc": 0, + } + + sensor_state = { + "imu": fake_imu, + "enc": fake_enc, + } + + sensor_variances = { + "imu": np.eye(len(x)) * 1, + "enc": np.eye(len(x)) * .1, + } + + # Simulate the state change with F and a dt of .1 100 times using matplotlib + for i in range(int(real_t / cycle_dt)): + true_states.append(next_state) + est_states.append(state) + + seed = random.random() + + # Adjust steering angle continuously + # next_state[5] = math.sin(i * cycle_dt / 2) * .79 + next_state[5] = math.sin(i * cycle_dt / 2) * .79 + + next_state, next_var = predict(next_state, next_var, cycle_dt) + plt.plot(next_state[0], next_state[1], 'bo') + + if seed < .8: + state, variance = predict(state, variance, cycle_dt) + elif seed < .9: + current_time = i * cycle_dt + last_update_time = last_update_time_matrix["imu"] + update_time_elapsed = current_time - last_update_time + last_update_time_matrix["imu"] = current_time + + state, variance = sensor_update( + state, + variance, + sensor_state["imu"](next_state), + sensor_mul_matrix["imu"], + sensor_variances["imu"], + cycle_dt, + update_time_elapsed + ) + elif seed < 1.0: + current_time = i * cycle_dt + last_update_time = last_update_time_matrix["enc"] + update_time_elapsed = current_time - last_update_time + last_update_time_matrix["enc"] = current_time + + state, variance = sensor_update( + state, + variance, + sensor_state["enc"](next_state), + sensor_mul_matrix["enc"], + sensor_variances["enc"], + cycle_dt, + update_time_elapsed + ) + + plt.plot(state[0], state[1], 'ro') + + true_states.append(next_state) + est_states.append(state) + + print("Root mean squared estimation error: ", root_means_squared_estimation_error(true_states, est_states)) + + +if __name__ == "__main__": + main_loop() + plt.show() diff --git a/cev/all/cev_localization_ros2/gen_jacobian.py b/cev/all/cev_localization_ros2/gen_jacobian.py new file mode 100644 index 0000000..ffa1402 --- /dev/null +++ b/cev/all/cev_localization_ros2/gen_jacobian.py @@ -0,0 +1,54 @@ +import sympy as sp + +# Define state variables +x, y, z = sp.symbols('x y z') +roll, pitch, yaw = sp.symbols('roll pitch yaw') +dx, dy, dz = sp.symbols('dx dy dz') +droll, dpitch, dyaw = sp.symbols('droll dpitch dyaw') +ddx, ddy, ddz = sp.symbols('ddx ddy ddz') +tau, dtau, ddtau = sp.symbols('tau dtau ddtau') +dt, wheelbase = sp.symbols('dt wheelbase') + +# New state definitions +new_x = x + dx * sp.cos(yaw + tau) * dt +new_y = y + dx * sp.sin(yaw + tau) * dt +new_dx = dx + ddx * dt +new_dy = dy + ddy * dt +new_dtau = dtau + ddtau * dt +new_dyaw = dx * sp.sin(tau) / wheelbase * dt +new_yaw = yaw + new_dyaw +new_tau = tau + dtau * dt + +# Combine into vectors +new_state = sp.Matrix([ + new_x, new_y, z, roll, pitch, new_yaw, + new_dx, new_dy, dz, droll, dpitch, new_dyaw, + ddx, ddy, ddz, new_tau, new_dtau, ddtau +]) + +current_state = sp.Matrix([ + x, y, z, roll, pitch, yaw, + dx, dy, dz, droll, dpitch, dyaw, + ddx, ddy, ddz, tau, dtau, ddtau +]) + +# Compute the Jacobian +jacobian = new_state.jacobian(current_state) + +# State names for mapping +state_names = [ + "x__", "y__", "z__", "roll__", "pitch__", "yaw__", + "d_x__", "d_y__", "d_z__", "d_roll__", "d_pitch__", "d_yaw__", + "d2_x__", "d2_y__", "d2_z__", "tau__", "d_tau__", "d2_tau__" +] + +# Generate C++ code for non-zero Jacobian entries +cpp_code = [] +for i in range(jacobian.shape[0]): + for j in range(jacobian.shape[1]): + entry = jacobian[i, j] + if entry not in [0, 1]: # Skip zero entries + cpp_code.append(f"F_k({state_names[i]}, {state_names[j]}) = {sp.ccode(entry)};") + +# Print the generated C++ code +print("\n".join(cpp_code)) diff --git a/cev/all/cev_localization_ros2/include/config_parser.h b/cev/all/cev_localization_ros2/include/config_parser.h new file mode 100644 index 0000000..1898073 --- /dev/null +++ b/cev/all/cev_localization_ros2/include/config_parser.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "estimator.h" + +namespace cev_localization { + namespace config_parser { + struct Sensor { + std::string type; + std::string topic; + std::optional frame_id; + std::vector state_mask; + double covariance_multiplier; + bool use_message_covariance; + std::vector estimator_models; + + static Sensor loadSensor(const YAML::Node& sensorNode); + }; + + struct Publish { + bool active = false; + std::string topic = ""; + int rate = 100; + std::string child_frame = ""; + std::string parent_frame = ""; + + bool publish_tf = false; + + static Publish loadPublish(const YAML::Node& publishNode); + }; + + struct UpdateModel { + std::string type; + std::vector state_mask; + std::vector estimator_models; + + Publish publish; + + static UpdateModel loadUpdateModel(const YAML::Node& modelNode); + }; + + struct Config { + // Sensors + std::unordered_map sensors; + + // Update Models + std::unordered_map update_models; + + static Config loadConfig(const YAML::Node& configNode); + }; + + class ConfigParser { + public: + static Config load(const std::string& filePath); + }; + } +} diff --git a/cev/all/cev_localization_ros2/include/ros_sensor.h b/cev/all/cev_localization_ros2/include/ros_sensor.h new file mode 100644 index 0000000..58ddd93 --- /dev/null +++ b/cev/all/cev_localization_ros2/include/ros_sensor.h @@ -0,0 +1,44 @@ +#pragma once + +#include "sensor.h" + +namespace cev_localization { + + template + class RosSensor : public ckf::Sensor { + protected: + ckf::M multiplier = ckf::M::Zero(); + + void new_time_handler(double time) { + previous_update_time = most_recent_update_time; + most_recent_update_time = time; + } + + public: + RosSensor(std::string topic, ckf::V state, ckf::M covariance, + std::vector> dependents) + : Sensor(state, covariance, dependents) { + this->topic = topic; + } + + std::string topic; + + /** + * Handle a new message. Meant to be used as or in a message subscriber. + */ + void msg_handler(typename T::SharedPtr msg) { + ckf::StatePackage update = msg_update(msg); + updateInternals(update); + update_dependents(); + } + + /** + * Update the state with a new message, and return the time of the new message + */ + virtual ckf::StatePackage msg_update(typename T::SharedPtr msg) = 0; + + ckf::M state_matrix_multiplier() { + return multiplier; + } + }; +} \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/include/std_ros_sensors.h b/cev/all/cev_localization_ros2/include/std_ros_sensors.h new file mode 100644 index 0000000..40a99a8 --- /dev/null +++ b/cev/all/cev_localization_ros2/include/std_ros_sensors.h @@ -0,0 +1,52 @@ +#pragma once + +#include "sensor_msgs/msg/imu.hpp" +#include "nav_msgs/msg/odometry.hpp" +#include "cev_msgs/msg/sensor_collect.hpp" +#include +#include + +#include "ros_sensor.h" +#include "estimator.h" + +using namespace ckf; + +namespace cev_localization { + namespace standard_ros_sensors { + + class IMUSensor : public RosSensor { + private: + double pos_mod(double angle); + + protected: + bool initialized; + bool relative; + double initial_yaw; + double last_reported_yaw; + double last_sensor_raw_yaw; + bool use_message_covariance = true; + + public: + IMUSensor(std::string topic, V state, M covariance, + std::vector> dependents, + std::vector state_mask = {"d2_x", "d2_y", "yaw"}, + bool use_message_covariance = true, bool relative = true); + + StatePackage msg_update(sensor_msgs::msg::Imu::SharedPtr msg); + }; + + class RawSensor : public RosSensor { + protected: + bool use_message_covariance = true; + + public: + RawSensor(std::string topic, V state, M covariance, + std::vector> dependents, + std::vector state_mask = {"d_x", "tau"}, + bool use_message_covariance = true); + + StatePackage msg_update(cev_msgs::msg::SensorCollect::SharedPtr msg); + }; + + } +} \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/launch/.gitkeep b/cev/all/cev_localization_ros2/launch/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cev/all/cev_localization_ros2/launch/debug_launch.py b/cev/all/cev_localization_ros2/launch/debug_launch.py new file mode 100644 index 0000000..50ee173 --- /dev/null +++ b/cev/all/cev_localization_ros2/launch/debug_launch.py @@ -0,0 +1,89 @@ +from launch import LaunchDescription +from launch_ros.actions import Node +from launch.actions import DeclareLaunchArgument +from ament_index_python.packages import get_package_share_directory +import os + + +def get_path(package, dir, file): + return os.path.join(get_package_share_directory(package), dir, file) + + +def generate_launch_description(): + rviz2_config = get_path("cev_localization", "config", "conf.rviz") + + return LaunchDescription( + [ + Node( + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_world_map", + output="screen", + arguments=[ + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "world", + "map", + ], + ), + Node( + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_map_odom", + output="screen", + arguments=[ + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "map", + "odom", + ], + ), + Node( + package="tf2_ros", + executable="static_transform_publisher", + name="static_transform_publisher_odom_base_link", + output="screen", + arguments=[ + "0", + "0", + "0", # Translation: x = 0.035, y = 0.04, z = 0 (meters) + "0", + "0", + "0", + "1", # Rotation: 0 + "odom", + "meow_link", + ], + ), + Node( + package="cev_localization", + executable="localization", + name="cev_localization_node", + output="screen", + parameters=[ + { + "config_file": get_path( + "cev_localization", "config", "ekf_real.yml" + ) + } + ], + ), + Node( + package="rviz2", + executable="rviz2", + arguments=["--display-config", rviz2_config], + name="rviz2", + output="screen", + ), + ] + ) diff --git a/cev/all/cev_localization_ros2/launch/launch.py b/cev/all/cev_localization_ros2/launch/launch.py new file mode 100644 index 0000000..026a960 --- /dev/null +++ b/cev/all/cev_localization_ros2/launch/launch.py @@ -0,0 +1,29 @@ +from launch import LaunchDescription +from launch_ros.actions import Node +from launch.actions import DeclareLaunchArgument +from ament_index_python.packages import get_package_share_directory +import os + + +def get_path(package, dir, file): + return os.path.join(get_package_share_directory(package), dir, file) + + +def generate_launch_description(): + return LaunchDescription( + [ + Node( + package="cev_localization", + executable="localization", + name="cev_localization_node", + output="screen", + parameters=[ + { + "config_file": get_path( + "cev_localization", "config", "ekf_real.yml" + ) + } + ], + ) + ] + ) diff --git a/cev/all/cev_localization_ros2/package.xml b/cev/all/cev_localization_ros2/package.xml new file mode 100644 index 0000000..8c5025c --- /dev/null +++ b/cev/all/cev_localization_ros2/package.xml @@ -0,0 +1,33 @@ + + + + cev_localization + 0.0.0 + TODO: Package description + sloth + TODO: License declaration + + ament_cmake + + rclcpp + sensor_msgs + std_msgs + eigen3 + tf2 + tf2_ros + tf2_geometry_msgs + nav_msgs + + cev_msgs + cev_kalman_filter + + yaml-cpp + + + ament_lint_auto + ament_lint_common + + + ament_cmake + + \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_1/ackermann_recording_v_1_0.db3 b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_1/ackermann_recording_v_1_0.db3 new file mode 100644 index 0000000..35837e6 Binary files /dev/null and b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_1/ackermann_recording_v_1_0.db3 differ diff --git a/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_1/metadata.yaml b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_1/metadata.yaml new file mode 100644 index 0000000..b86d8b9 --- /dev/null +++ b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_1/metadata.yaml @@ -0,0 +1,98 @@ +rosbag2_bagfile_information: + version: 5 + storage_identifier: sqlite3 + duration: + nanoseconds: 49551146185 + starting_time: + nanoseconds_since_epoch: 1732241210235021710 + message_count: 18856 + topics_with_message_count: + - topic_metadata: + name: /tf + type: tf2_msgs/msg/TFMessage + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 1461 + - topic_metadata: + name: /rc_movement_msg + type: ackermann_msgs/msg/AckermannDrive + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 2841 + - topic_metadata: + name: /encoder_odometry + type: nav_msgs/msg/Odometry + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 2283 + - topic_metadata: + name: /odometry/filtered + type: nav_msgs/msg/Odometry + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 1461 + - topic_metadata: + name: /tf_static + type: tf2_msgs/msg/TFMessage + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 5 + - topic_metadata: + name: /events/write_split + type: rosbag2_interfaces/msg/WriteSplitEvent + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 0 + - topic_metadata: + name: /auto_max_vel + type: std_msgs/msg/Float32 + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 4885 + - topic_metadata: + name: /joy + type: sensor_msgs/msg/Joy + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 3103 + - topic_metadata: + name: /diagnostics + type: diagnostic_msgs/msg/DiagnosticArray + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 97 + - topic_metadata: + name: /parameter_events + type: rcl_interfaces/msg/ParameterEvent + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 0 + - topic_metadata: + name: /sensor_collect + type: cev_msgs/msg/SensorCollect + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 2325 + - topic_metadata: + name: /rosout + type: rcl_interfaces/msg/Log + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 17 + - topic_metadata: + name: /scan + type: sensor_msgs/msg/LaserScan + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 378 + compression_format: "" + compression_mode: "" + relative_file_paths: + - ackermann_recording_v_1_0.db3 + files: + - path: ackermann_recording_v_1_0.db3 + starting_time: + nanoseconds_since_epoch: 1732241210235021710 + duration: + nanoseconds: 49551146185 + message_count: 18856 \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_2/ackermann_recording_v_2_0.db3 b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_2/ackermann_recording_v_2_0.db3 new file mode 100644 index 0000000..df4740b Binary files /dev/null and b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_2/ackermann_recording_v_2_0.db3 differ diff --git a/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_2/metadata.yaml b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_2/metadata.yaml new file mode 100644 index 0000000..8ee250e --- /dev/null +++ b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_2/metadata.yaml @@ -0,0 +1,116 @@ +rosbag2_bagfile_information: + version: 5 + storage_identifier: sqlite3 + duration: + nanoseconds: 49878255986 + starting_time: + nanoseconds_since_epoch: 1732324873007091423 + message_count: 19079 + topics_with_message_count: + - topic_metadata: + name: /scan + type: sensor_msgs/msg/LaserScan + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 329 + - topic_metadata: + name: /odometry/filtered + type: nav_msgs/msg/Odometry + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 1497 + - topic_metadata: + name: /parameter_events + type: rcl_interfaces/msg/ParameterEvent + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 0 + - topic_metadata: + name: /tf_static + type: tf2_msgs/msg/TFMessage + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 5 + - topic_metadata: + name: /tf + type: tf2_msgs/msg/TFMessage + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 1500 + - topic_metadata: + name: /auto_max_vel + type: std_msgs/msg/Float32 + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 4979 + - topic_metadata: + name: /joy + type: sensor_msgs/msg/Joy + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 2648 + - topic_metadata: + name: /sensor_collect + type: cev_msgs/msg/SensorCollect + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 2339 + - topic_metadata: + name: /rc_movement_msg + type: ackermann_msgs/msg/AckermannDrive + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 2558 + - topic_metadata: + name: /events/write_split + type: rosbag2_interfaces/msg/WriteSplitEvent + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 0 + - topic_metadata: + name: /orientation + type: geometry_msgs/msg/Quaternion + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 0 + - topic_metadata: + name: /imu + type: sensor_msgs/msg/Imu + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 389 + - topic_metadata: + name: /diagnostics + type: diagnostic_msgs/msg/DiagnosticArray + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 99 + - topic_metadata: + name: /rosout + type: rcl_interfaces/msg/Log + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 12 + - topic_metadata: + name: /encoder_odometry + type: nav_msgs/msg/Odometry + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 2336 + - topic_metadata: + name: /magnetometer + type: sensor_msgs/msg/MagneticField + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 388 + compression_format: "" + compression_mode: "" + relative_file_paths: + - ackermann_recording_v_2_0.db3 + files: + - path: ackermann_recording_v_2_0.db3 + starting_time: + nanoseconds_since_epoch: 1732324873007091423 + duration: + nanoseconds: 49878255986 + message_count: 19079 \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_3/ackermann_recording_v_3_0.db3 b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_3/ackermann_recording_v_3_0.db3 new file mode 100644 index 0000000..6f3335f Binary files /dev/null and b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_3/ackermann_recording_v_3_0.db3 differ diff --git a/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_3/metadata.yaml b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_3/metadata.yaml new file mode 100644 index 0000000..f458918 --- /dev/null +++ b/cev/all/cev_localization_ros2/rosbag/ackermann_recording_v_3/metadata.yaml @@ -0,0 +1,116 @@ +rosbag2_bagfile_information: + version: 5 + storage_identifier: sqlite3 + duration: + nanoseconds: 23004854249 + starting_time: + nanoseconds_since_epoch: 1732436633915847685 + message_count: 8397 + topics_with_message_count: + - topic_metadata: + name: /tf + type: tf2_msgs/msg/TFMessage + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 633 + - topic_metadata: + name: /tf_static + type: tf2_msgs/msg/TFMessage + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 6 + - topic_metadata: + name: /scan + type: sensor_msgs/msg/LaserScan + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 172 + - topic_metadata: + name: /odometry/filtered + type: nav_msgs/msg/Odometry + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 634 + - topic_metadata: + name: /parameter_events + type: rcl_interfaces/msg/ParameterEvent + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 0 + - topic_metadata: + name: /sensor_collect + type: cev_msgs/msg/SensorCollect + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 997 + - topic_metadata: + name: /diagnostics + type: diagnostic_msgs/msg/DiagnosticArray + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 42 + - topic_metadata: + name: /rosout + type: rcl_interfaces/msg/Log + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 93 + - topic_metadata: + name: /imu + type: sensor_msgs/msg/Imu + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 231 + - topic_metadata: + name: /orientation + type: geometry_msgs/msg/Quaternion + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 0 + - topic_metadata: + name: /events/write_split + type: rosbag2_interfaces/msg/WriteSplitEvent + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 0 + - topic_metadata: + name: /auto_max_vel + type: std_msgs/msg/Float32 + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 2284 + - topic_metadata: + name: /joy + type: sensor_msgs/msg/Joy + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 1016 + - topic_metadata: + name: /magnetometer + type: sensor_msgs/msg/MagneticField + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 231 + - topic_metadata: + name: /encoder_odometry + type: nav_msgs/msg/Odometry + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 1077 + - topic_metadata: + name: /rc_movement_msg + type: ackermann_msgs/msg/AckermannDrive + serialization_format: cdr + offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false" + message_count: 981 + compression_format: "" + compression_mode: "" + relative_file_paths: + - ackermann_recording_v_3_0.db3 + files: + - path: ackermann_recording_v_3_0.db3 + starting_time: + nanoseconds_since_epoch: 1732436633915847685 + duration: + nanoseconds: 23004854249 + message_count: 8397 \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/src/ackermann_ekf.cpp b/cev/all/cev_localization_ros2/src/ackermann_ekf.cpp new file mode 100644 index 0000000..35046d5 --- /dev/null +++ b/cev/all/cev_localization_ros2/src/ackermann_ekf.cpp @@ -0,0 +1,206 @@ +#include +#include +#include + +#include "model.h" +#include "ros_sensor.h" +#include "config_parser.h" +#include "model.h" +#include "sensor.h" +#include "standard_models.h" +#include "std_ros_sensors.h" + +#include + +#include + +using std::placeholders::_1; + +using namespace ckf; +using namespace cev_localization; + +class LocalizationNode : public rclcpp::Node { +public: + LocalizationNode(): Node("CEVLocalizationNode") { + RCLCPP_INFO(this->get_logger(), "Initializing CEV Localization Node"); + + this->declare_parameter("config_file", "config/ekf_real.yml"); + std::string config_file_path = this->get_parameter("config_file").as_string(); + + RCLCPP_INFO(this->get_logger(), "Parsing config file at %s", config_file_path.c_str()); + + // Load the YAML configurations + config = config_parser::ConfigParser::loadConfig(config_file_path); + + std::unordered_map> update_models; + + for (std::pair model: config.update_models) { + std::string name = model.first; + config_parser::UpdateModel& mod = model.second; + + if (update_models.find(name) != update_models.end()) { + RCLCPP_ERROR(this->get_logger(), "Model `%s` already exists", name.c_str()); + throw std::runtime_error("Model already exists"); + } + + if (mod.type == "ACKERMANN") { + update_models[name] = std::make_shared( + V::Zero(), M::Identity() * .1, M::Identity() * .1, .185, mod.state_mask); + } else if (mod.type == "CARTESIAN") { + update_models[name] = std::make_shared( + V::Zero(), M::Identity() * .1, M::Identity() * .1, mod.state_mask); + } else { + RCLCPP_ERROR(this->get_logger(), "Unknown model type: `%s`", mod.type.c_str()); + throw std::runtime_error("Unknown model type"); + } + } + + for (std::pair sensor: config.sensors) { + std::string name = sensor.first; + config_parser::Sensor& sen = sensor.second; + + if (sensors.find(name) != sensors.end()) { + RCLCPP_ERROR(this->get_logger(), "Sensor `%s` already exists", name.c_str()); + throw std::runtime_error("Sensor already exists"); + } + + // Create array for models to bind to + std::vector> models; + for (std::string& model_name: sen.estimator_models) { + if (update_models.find(model_name) == update_models.end()) { + RCLCPP_ERROR(this->get_logger(), "Model `%s` does not exist", + model_name.c_str()); + throw std::runtime_error("Model does not exist"); + } else { + models.push_back(update_models[model_name]); + } + } + + if (sen.type == "IMU") { + auto sensor = std::make_shared(sen.topic, + V::Zero(), M::Identity() * .1, models, sen.state_mask, + sen.use_message_covariance); + + sensors[name] = sensor; + + sensor_subscribers[name] = this->create_subscription( + sen.topic, 10, [sensor](const sensor_msgs::msg::Imu::SharedPtr msg) { + sensor->msg_handler(msg); + }); + + } else if (sen.type == "RAW") { + auto sensor = std::make_shared(sen.topic, + V::Zero(), M::Identity() * .1, models, sen.state_mask, + sen.use_message_covariance); + + sensors[name] = sensor; + + sensor_subscribers[name] = this->create_subscription( + sen.topic, 10, [sensor](const cev_msgs::msg::SensorCollect::SharedPtr msg) { + sensor->msg_handler(msg); + }); + } else { + RCLCPP_ERROR(this->get_logger(), "Unknown sensor type: `%s`", sen.type.c_str()); + throw std::runtime_error("Unknown sensor type"); + } + } + + if (update_models.find(config.main_model) == update_models.end()) { + RCLCPP_ERROR(this->get_logger(), "Main model `%s` does not exist", + config.main_model.c_str()); + throw std::runtime_error("Main model does not exist"); + } + + RCLCPP_INFO(this->get_logger(), "Configuration file parsed! Finishing initialization."); + + main_model = update_models[config.main_model].get(); + + timer = this->create_wall_timer(std::chrono::milliseconds((int)(config.time_step * 1000.0)), + std::bind(&LocalizationNode::timer_callback, this)); + + tf_broadcaster_ = std::make_shared(this); + odom_pub = this->create_publisher(config.odometry_topic, 1); + } + +private: + config_parser::Config config; + + std::unordered_map sensor_subscribers; + std::unordered_map> update_models; + std::unordered_map> sensors; + + std::shared_ptr tf_broadcaster_; + rclcpp::TimerBase::SharedPtr timer; + rclcpp::Publisher::SharedPtr odom_pub; + + ckf::Model* main_model; + + void timer_callback() { + double time = get_clock()->now().seconds(); + + // model->update(time); + V state = main_model->get_state(); + M covariance = main_model->get_covariance(); + + // std::pair prediction = main_model->predict(time); + // V state = prediction.first; + // M covariance = prediction.second; + + nav_msgs::msg::Odometry odom_msg; + odom_msg.header.stamp = this->now(); + odom_msg.header.frame_id = config.odom_frame; + odom_msg.child_frame_id = config.base_link_frame; + + odom_msg.pose.pose.position.x = state[ckf::state::x]; + odom_msg.pose.pose.position.y = state[ckf::state::y]; + odom_msg.pose.pose.position.z = 0.0; + + odom_msg.pose.covariance[0] = covariance(ckf::state::x, ckf::state::x); + odom_msg.pose.covariance[7] = covariance(ckf::state::y, ckf::state::y); + odom_msg.pose.covariance[35] = covariance(ckf::state::yaw, ckf::state::yaw); + + tf2::Quaternion q = tf2::Quaternion(); + q.setRPY(0, 0, state[ckf::state::yaw]); + q = q.normalized(); + odom_msg.pose.pose.orientation.x = q.x(); + odom_msg.pose.pose.orientation.y = q.y(); + odom_msg.pose.pose.orientation.z = q.z(); + odom_msg.pose.pose.orientation.w = q.w(); + + odom_msg.twist.twist.linear.x = state[ckf::state::d_x]; + odom_msg.twist.twist.linear.y = state[ckf::state::d_y]; + odom_msg.twist.twist.angular.z = state[ckf::state::d_yaw]; + + odom_msg.twist.covariance[0] = covariance(ckf::state::d_x, ckf::state::d_x); + odom_msg.twist.covariance[7] = covariance(ckf::state::d_y, ckf::state::d_y); + odom_msg.twist.covariance[35] = covariance(ckf::state::d_yaw, ckf::state::d_yaw); + + odom_pub->publish(odom_msg); + + if (config.publish_tf) { + geometry_msgs::msg::TransformStamped transformStamped; + + transformStamped.header.stamp = this->now(); + transformStamped.header.frame_id = config.odom_frame; + transformStamped.child_frame_id = config.base_link_frame; + + transformStamped.transform.translation.x = state[ckf::state::x]; + transformStamped.transform.translation.y = state[ckf::state::y]; + transformStamped.transform.translation.z = state[ckf::state::z]; + + transformStamped.transform.rotation.x = q.x(); + transformStamped.transform.rotation.y = q.y(); + transformStamped.transform.rotation.z = q.z(); + transformStamped.transform.rotation.w = q.w(); + + tf_broadcaster_->sendTransform(transformStamped); + } + } +}; + +int main(int argc, char* argv[]) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/src/config_parser.cpp b/cev/all/cev_localization_ros2/src/config_parser.cpp new file mode 100644 index 0000000..6c892fa --- /dev/null +++ b/cev/all/cev_localization_ros2/src/config_parser.cpp @@ -0,0 +1,91 @@ +#include "config_parser.h" + +using namespace cev_localization::config_parser; + +Sensor Sensor::loadSensor(const YAML::Node& sensorNode) { + if (!sensorNode["type"] || !sensorNode["topic"]) { + throw std::runtime_error("Sensor type and topic must be defined"); + } + + Sensor sensor; + sensor.type = sensorNode["type"].as(); + sensor.topic = sensorNode["topic"].as(); + sensor.frame_id = sensorNode["frame_id"] + ? std::make_optional(sensorNode["frame_id"].as()) + : std::nullopt; + sensor.state_mask = sensorNode["state"] ? sensorNode["state"].as>() + : std::vector{}; + sensor.covariance_multiplier = sensorNode["covariance_multiplier"].as(1.0); + sensor.use_message_covariance = sensorNode["use_message_covariance"].as(true); + sensor.estimator_models = sensorNode["estimator_models"] + ? sensorNode["estimator_models"].as>() + : std::vector{}; + + return sensor; +} + +Publish Publish::loadPublish(const YAML::Node& publishNode) { + Publish publish; + + if (!publishNode["active"]) { + return publish; + } + + publish.active = publishNode["active"].as(); + + if (!publishNode["topic"] || !publishNode["child_frame"] || !publishNode["parent_frame"]) { + throw std::runtime_error("Publish topic and frames must be defined"); + } + + publish.topic = publishNode["topic"].as(); + publish.rate = publishNode["rate"].as(100); + publish.child_frame = publishNode["child_frame"].as(""); + publish.parent_frame = publishNode["parent_frame"].as(""); + + publish.publish_tf = publishNode["publish_tf"].as(false); + + return publish; +} + +UpdateModel UpdateModel::loadUpdateModel(const YAML::Node& modelNode) { + if (!modelNode["type"] || !modelNode["state"]) { + throw std::runtime_error("Model type must be defined"); + } + + UpdateModel model; + model.type = modelNode["type"].as(); + model.state_mask = modelNode["state"].as>(); + model.estimator_models = modelNode["estimator_models"] + ? modelNode["estimator_models"].as>() + : std::vector{}; + + if (!modelNode["publish"]) { + return model; + } + + model.publish = Publish::loadPublish(modelNode["publish"]); + + return model; +} + +Config Config::loadConfig(const YAML::Node& configNode) { + Config config; + + for (const auto& sensorEntry: configNode["sensors"]) { + const std::string sensorName = sensorEntry.first.as(); + config.sensors[sensorName] = Sensor::loadSensor(sensorEntry.second); + } + + for (const auto& modelEntry: configNode["update_models"]) { + const std::string modelName = modelEntry.first.as(); + config.update_models[modelName] = UpdateModel::loadUpdateModel(modelEntry.second); + } + + return config; +} + +Config ConfigParser::load(const std::string& filePath) { + YAML::Node configNode = YAML::LoadFile(filePath); + + return Config::loadConfig(configNode); +} \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/src/localization.cpp b/cev/all/cev_localization_ros2/src/localization.cpp new file mode 100644 index 0000000..61f1eeb --- /dev/null +++ b/cev/all/cev_localization_ros2/src/localization.cpp @@ -0,0 +1,201 @@ +#include +#include +#include + +#include "model.h" +#include "ros_sensor.h" +#include "config_parser.h" +#include "model.h" +#include "sensor.h" +#include "standard_models.h" +#include "std_ros_sensors.h" + +#include + +#include + +using std::placeholders::_1; + +using namespace ckf; +using namespace cev_localization; + +class LocalizationNode : public rclcpp::Node { +public: + LocalizationNode(): Node("CEVLocalizationNode") { + RCLCPP_INFO(this->get_logger(), "Initializing CEV Localization Node"); + + this->declare_parameter("config_file", "config/ekf_real.yml"); + std::string config_file_path = this->get_parameter("config_file").as_string(); + + RCLCPP_INFO(this->get_logger(), "Parsing config file at %s", config_file_path.c_str()); + + // Load the YAML configurations + config = config_parser::ConfigParser::load(config_file_path); + + std::unordered_map> update_models; + + for (std::pair model: config.update_models) { + std::string name = model.first; + config_parser::UpdateModel& mod = model.second; + + if (update_models.find(name) != update_models.end()) { + RCLCPP_ERROR(this->get_logger(), "Model `%s` already exists", name.c_str()); + throw std::runtime_error("Model already exists"); + } + + if (mod.type == "ACKERMANN") { + update_models[name] = std::make_shared( + V::Zero(), M::Identity() * .1, M::Identity() * .1, .185, mod.state_mask); + } else if (mod.type == "CARTESIAN") { + update_models[name] = std::make_shared( + V::Zero(), M::Identity() * .1, M::Identity() * .1, mod.state_mask); + } else { + RCLCPP_ERROR(this->get_logger(), "Unknown model type: `%s`", mod.type.c_str()); + throw std::runtime_error("Unknown model type"); + } + + if (mod.publish.active) { + filter_publishers[name] = + this->create_publisher(mod.publish.topic, 1); + + ckf::Model* model_ = update_models[name].get(); + + // Create a timer to publish the model + timers[name] = this->create_wall_timer( + std::chrono::milliseconds((int)(1000 / mod.publish.rate)), + [this, model_, name]() { this->publish_model(model_, name); }); + } + } + + for (std::pair sensor: config.sensors) { + std::string name = sensor.first; + config_parser::Sensor& sen = sensor.second; + + if (sensors.find(name) != sensors.end()) { + RCLCPP_ERROR(this->get_logger(), "Sensor `%s` already exists", name.c_str()); + throw std::runtime_error("Sensor already exists"); + } + + // Create array for models to bind to + std::vector> models; + for (std::string& model_name: sen.estimator_models) { + if (update_models.find(model_name) == update_models.end()) { + RCLCPP_ERROR(this->get_logger(), "Model `%s` does not exist", + model_name.c_str()); + throw std::runtime_error("Model does not exist"); + } else { + models.push_back(update_models[model_name]); + } + } + + if (sen.type == "IMU") { + auto sensor = std::make_shared(sen.topic, + V::Zero(), M::Identity() * .1, models, sen.state_mask, + sen.use_message_covariance); + + sensors[name] = sensor; + + sensor_subscribers[name] = this->create_subscription( + sen.topic, 10, [sensor](const sensor_msgs::msg::Imu::SharedPtr msg) { + sensor->msg_handler(msg); + }); + + } else if (sen.type == "RAW") { + auto sensor = std::make_shared(sen.topic, + V::Zero(), M::Identity() * .1, models, sen.state_mask, + sen.use_message_covariance); + + sensors[name] = sensor; + + sensor_subscribers[name] = this->create_subscription( + sen.topic, 10, [sensor](const cev_msgs::msg::SensorCollect::SharedPtr msg) { + sensor->msg_handler(msg); + }); + } else { + RCLCPP_ERROR(this->get_logger(), "Unknown sensor type: `%s`", sen.type.c_str()); + throw std::runtime_error("Unknown sensor type"); + } + } + + RCLCPP_INFO(this->get_logger(), "Configuration file parsed! Finishing initialization."); + + tf_broadcaster_ = std::make_shared(this); + } + +private: + config_parser::Config config; + + std::unordered_map sensor_subscribers; + std::unordered_map::SharedPtr> + filter_publishers; + std::unordered_map timers; + + std::unordered_map> update_models; + std::unordered_map> sensors; + + std::shared_ptr tf_broadcaster_; + rclcpp::TimerBase::SharedPtr timer; + + void publish_model(ckf::Model* model_, std::string model) { + V state = model_->get_state(); + M covariance = model_->get_covariance(); + + nav_msgs::msg::Odometry odom_msg; + odom_msg.header.stamp = this->now(); + odom_msg.header.frame_id = this->config.update_models[model].publish.parent_frame; + odom_msg.child_frame_id = this->config.update_models[model].publish.child_frame; + + odom_msg.pose.pose.position.x = state[ckf::state::x]; + odom_msg.pose.pose.position.y = state[ckf::state::y]; + odom_msg.pose.pose.position.z = 0.0; + + odom_msg.pose.covariance[0] = covariance(ckf::state::x, ckf::state::x); + odom_msg.pose.covariance[7] = covariance(ckf::state::y, ckf::state::y); + odom_msg.pose.covariance[35] = covariance(ckf::state::yaw, ckf::state::yaw); + + tf2::Quaternion q = tf2::Quaternion(); + q.setRPY(0, 0, state[ckf::state::yaw]); + q = q.normalized(); + odom_msg.pose.pose.orientation.x = q.x(); + odom_msg.pose.pose.orientation.y = q.y(); + odom_msg.pose.pose.orientation.z = q.z(); + odom_msg.pose.pose.orientation.w = q.w(); + + odom_msg.twist.twist.linear.x = state[ckf::state::d_x]; + odom_msg.twist.twist.linear.y = state[ckf::state::d_y]; + odom_msg.twist.twist.angular.z = state[ckf::state::d_yaw]; + + odom_msg.twist.covariance[0] = covariance(ckf::state::d_x, ckf::state::d_x); + odom_msg.twist.covariance[7] = covariance(ckf::state::d_y, ckf::state::d_y); + odom_msg.twist.covariance[35] = covariance(ckf::state::d_yaw, ckf::state::d_yaw); + + this->filter_publishers[model]->publish(odom_msg); + + if (this->config.update_models[model].publish.publish_tf) { + geometry_msgs::msg::TransformStamped transformStamped; + + transformStamped.header.stamp = this->now(); + transformStamped.header.frame_id = + this->config.update_models[model].publish.parent_frame; + transformStamped.child_frame_id = this->config.update_models[model].publish.child_frame; + + transformStamped.transform.translation.x = state[ckf::state::x]; + transformStamped.transform.translation.y = state[ckf::state::y]; + transformStamped.transform.translation.z = state[ckf::state::z]; + + transformStamped.transform.rotation.x = q.x(); + transformStamped.transform.rotation.y = q.y(); + transformStamped.transform.rotation.z = q.z(); + transformStamped.transform.rotation.w = q.w(); + + this->tf_broadcaster_->sendTransform(transformStamped); + } + } +}; + +int main(int argc, char* argv[]) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/src/ros_sensor.cpp b/cev/all/cev_localization_ros2/src/ros_sensor.cpp new file mode 100644 index 0000000..8e280c7 --- /dev/null +++ b/cev/all/cev_localization_ros2/src/ros_sensor.cpp @@ -0,0 +1 @@ +#include "ros_sensor.h" \ No newline at end of file diff --git a/cev/all/cev_localization_ros2/src/std_ros_sensors.cpp b/cev/all/cev_localization_ros2/src/std_ros_sensors.cpp new file mode 100644 index 0000000..24ae972 --- /dev/null +++ b/cev/all/cev_localization_ros2/src/std_ros_sensors.cpp @@ -0,0 +1,106 @@ +#include "std_ros_sensors.h" +#include + +using namespace cev_localization::standard_ros_sensors; + +/* IMU SENSOR */ + +double IMUSensor::pos_mod(double angle) { + return fmod(fmod(angle, 2 * M_PI) + 2 * M_PI, 2 * M_PI); +} + +IMUSensor::IMUSensor(std::string topic, V state, M covariance, + std::vector> dependents, std::vector state_mask, + bool use_message_covariance, bool relative) + : RosSensor(topic, state, covariance, dependents), + initialized(false), + relative(relative), + initial_yaw(0), + last_reported_yaw(0), + last_sensor_raw_yaw(0) { + multiplier = Estimator::state_mask_to_matrix(state_mask); + this->use_message_covariance = use_message_covariance; +} + +StatePackage IMUSensor::msg_update(sensor_msgs::msg::Imu::SharedPtr msg) { + StatePackage estimate = get_internals(); + estimate.update_time = msg->header.stamp.sec + (msg->header.stamp.nanosec / 1e9); + + // -1 means that the corresponding estimate is invalid + if (msg->linear_acceleration_covariance[0] != -1) { + estimate.state[ckf::state::d2_x] = msg->linear_acceleration.x; + estimate.state[ckf::state::d2_y] = msg->linear_acceleration.y; + + if (this->use_message_covariance) { + estimate.covariance(ckf::state::d2_x, + ckf::state::d2_x) = msg->linear_acceleration_covariance[0]; + estimate.covariance(ckf::state::d2_x, + ckf::state::d2_y) = msg->linear_acceleration_covariance[1]; + estimate.covariance(ckf::state::d2_y, + ckf::state::d2_x) = msg->linear_acceleration_covariance[3]; + estimate.covariance(ckf::state::d2_y, + ckf::state::d2_y) = msg->linear_acceleration_covariance[4]; + } + } + + if (msg->orientation_covariance[0] != -1) { + tf2::Quaternion q(msg->orientation.x, msg->orientation.y, msg->orientation.z, + msg->orientation.w); + tf2::Matrix3x3 m(q); + double roll, pitch, yaw; + m.getRPY(roll, pitch, yaw); + + if (relative && !initialized) { + last_sensor_raw_yaw = yaw; + last_reported_yaw = 0.0; + initialized = true; + + estimate.state[ckf::state::yaw] = 0.0; + return estimate; + } + + double modded_yaw_diff = fmod(yaw - last_sensor_raw_yaw, 2 * M_PI); + + if (modded_yaw_diff > M_PI) { + modded_yaw_diff -= 2 * M_PI; + } else if (modded_yaw_diff < -M_PI) { + modded_yaw_diff += 2 * M_PI; + } + + last_reported_yaw += modded_yaw_diff; + last_sensor_raw_yaw = yaw; + + estimate.state[ckf::state::yaw] = last_reported_yaw; + + if (this->use_message_covariance) { + estimate.covariance(ckf::state::yaw, ckf::state::yaw) = msg->orientation_covariance[8]; + } + } + + return estimate; +} + +/* RAW SENSOR */ + +RawSensor::RawSensor(std::string topic, V state, M covariance, + std::vector> dependents, std::vector state_mask, + bool use_message_covariance) + : RosSensor(topic, state, covariance, dependents) { + multiplier = Estimator::state_mask_to_matrix(state_mask); + this->use_message_covariance = use_message_covariance; +} + +StatePackage RawSensor::msg_update(cev_msgs::msg::SensorCollect::SharedPtr msg) { + StatePackage estimate = get_internals(); + estimate.update_time = msg->timestamp; + + estimate.state[ckf::state::d_x] = msg->velocity; + estimate.state[ckf::state::tau] = msg->steering_angle; + + // if (this->use_message_covariance) { // TODO: Implement covariance + // estimate.covariance(ckf::state::d_x, ckf::state::d_x) = msg->velocity_covariance; + // estimate.covariance(ckf::state::tau, ckf::state::tau) = msg->steering_angle_covariance; + // } + + return estimate; +} \ No newline at end of file diff --git a/cev/all/cev_msgs/CMakeLists.txt b/cev/all/cev_msgs/CMakeLists.txt new file mode 100644 index 0000000..1a6c8db --- /dev/null +++ b/cev/all/cev_msgs/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.8) +project(cev_msgs) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rosidl_default_generators REQUIRED) +find_package(std_msgs REQUIRED) +find_package(sensor_msgs REQUIRED) + +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +rosidl_generate_interfaces(${PROJECT_NAME} + "msg/Waypoint.msg" + "msg/SensorCollect.msg" + "msg/Trajectory.msg" + "msg/Obstacle.msg" + "msg/Obstacles.msg" + DEPENDENCIES std_msgs sensor_msgs +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() + diff --git a/cev/all/cev_msgs/README.md b/cev/all/cev_msgs/README.md new file mode 100644 index 0000000..d8be1c1 --- /dev/null +++ b/cev/all/cev_msgs/README.md @@ -0,0 +1,26 @@ +Custom ROS 2 messages for Cornell Electric Vehicles. + +SensorCollect.msg + - float64 : timestamp [ Message time in s ] + - float32 : velocity [ Velocity in m/s] + - float32 : steering_angle [ Steering angle in radians ] + +Trajectory.msg + - Header : header [ Header ] + - Waypoint[] : waypoints [ Array of Waypoints ] + +Waypoint.msg + - float32 : x [ x-coordinate in meters ] + - float32 : y [ y-coordinate in meters ] + - float32 : v [ velocity in m/s ] + - float32 : tau [ steering angle in radians ] + - float32 : theta [ heading in radians ] + +Obstacle.msg + - float64 : x [ x-coordinate of obstacle center in meters ] + - float64 : y [ y-coordinate of obstacle center in meters ] + - float64 : z [ z-coordinate of obstacle center in meters ] + - float64 : max_radius [ maximum radius of obstacle in meters ] + +Obstacles.msg + - Obstacle[] : obstacles [ Array of Obstacle(s) ] \ No newline at end of file diff --git a/cev/all/cev_msgs/msg/Obstacle.msg b/cev/all/cev_msgs/msg/Obstacle.msg new file mode 100644 index 0000000..601b9b6 --- /dev/null +++ b/cev/all/cev_msgs/msg/Obstacle.msg @@ -0,0 +1,11 @@ +# Obstacle.msg +# +# float64 : x +# float64 : y +# float64 : z +# float64 : max_radius + +float64 x +float64 y +float64 z +float64 max_radius \ No newline at end of file diff --git a/cev/all/cev_msgs/msg/Obstacles.msg b/cev/all/cev_msgs/msg/Obstacles.msg new file mode 100644 index 0000000..83c30ca --- /dev/null +++ b/cev/all/cev_msgs/msg/Obstacles.msg @@ -0,0 +1,7 @@ +# Obstacle.msg +# +# Obstacle[] : obstacles + +# cev_msgs/Obstacle[] obstacles + +sensor_msgs/PointCloud2[] obstacles \ No newline at end of file diff --git a/cev/all/cev_msgs/msg/SensorCollect.msg b/cev/all/cev_msgs/msg/SensorCollect.msg new file mode 100644 index 0000000..4cb515a --- /dev/null +++ b/cev/all/cev_msgs/msg/SensorCollect.msg @@ -0,0 +1,9 @@ +# SensorCollect.msg +# +# float64 : timestamp [ Message time ] +# float32 : velocity [ Velocity in m/s] +# float32 : steering_angle [ Steering angle in radians ] +# +float64 timestamp +float32 velocity +float32 steering_angle diff --git a/cev/all/cev_msgs/msg/Trajectory.msg b/cev/all/cev_msgs/msg/Trajectory.msg new file mode 100644 index 0000000..c052c17 --- /dev/null +++ b/cev/all/cev_msgs/msg/Trajectory.msg @@ -0,0 +1,8 @@ +# Trajectory.msg +# +# Header : header [ Header ] +# Waypoint[] : waypoints [ Array of Waypoints ] +# +std_msgs/Header header +cev_msgs/Waypoint[] waypoints +float32 timestep diff --git a/cev/all/cev_msgs/msg/Waypoint.msg b/cev/all/cev_msgs/msg/Waypoint.msg new file mode 100644 index 0000000..8b406ca --- /dev/null +++ b/cev/all/cev_msgs/msg/Waypoint.msg @@ -0,0 +1,11 @@ +# Waypoint.msg +# +# float32 : x [ x-coordinate in meters ] +# float32 : y [ y-coordinate in meters ] +# float32 : v [ velocity in m/s ] +# +float32 x +float32 y +float32 v +float32 tau +float32 theta diff --git a/cev/all/cev_msgs/package.xml b/cev/all/cev_msgs/package.xml new file mode 100644 index 0000000..b090353 --- /dev/null +++ b/cev/all/cev_msgs/package.xml @@ -0,0 +1,25 @@ + + + + cev_msgs + 0.0.0 + TODO: Package description + sloth + TODO: License declaration + + ament_cmake + + std_msgs + sensor_msgs + + rosidl_default_generators + rosidl_default_runtime + rosidl_interface_packages + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/cev/all/cev_planner_ros2/.clang-format b/cev/all/cev_planner_ros2/.clang-format new file mode 100644 index 0000000..1d9b175 --- /dev/null +++ b/cev/all/cev_planner_ros2/.clang-format @@ -0,0 +1,36 @@ +# CEV C++ setup +--- +BasedOnStyle: Google +IndentWidth: 4 +Language: Cpp +UseTab: Never +DerivePointerAlignment: false +PointerAlignment: Left +BreakStringLiterals: true +ReflowComments: true +ContinuationIndentWidth: 4 +ColumnLimit: 100 +AlignAfterOpenBracket: DontAlign +AlignTrailingComments: + Kind: Always + OverEmptyLines: 2 +AlignOperands: Align +BreakBeforeBinaryOperators: NonAssignment +PenaltyBreakAssignment: 60 +PenaltyBreakOpenParenthesis: 50 +IndentCaseLabels: true +AllowShortFunctionsOnASingleLine: Empty +SortIncludes: false +MacroBlockBegin: "^#if .*$" +MacroBlockEnd: "^#endif$" +AlignEscapedNewlines: Right +IndentPPDirectives: BeforeHash +IndentAccessModifiers: false +AccessModifierOffset: -4 +SpaceBeforeRangeBasedForLoopColon: false +SpaceBeforeCaseColon: false +SpaceBeforeCtorInitializerColon: false +SpaceAfterTemplateKeyword: false +NamespaceIndentation: All +FixNamespaceComments: false +--- diff --git a/cev/all/cev_planner_ros2/.devcontainer/Dockerfile.dev b/cev/all/cev_planner_ros2/.devcontainer/Dockerfile.dev new file mode 100644 index 0000000..626dbf7 --- /dev/null +++ b/cev/all/cev_planner_ros2/.devcontainer/Dockerfile.dev @@ -0,0 +1,77 @@ +# Start with the official ROS Humble ROS-Core image based on Ubuntu Jammy +FROM ros:humble-ros-core-jammy + +SHELL ["/bin/bash", "-c"] + +# Install basic build tools and ROS2 development dependencies +RUN apt-get update && apt-get install --no-install-recommends -y \ + build-essential \ + git \ + python3-colcon-common-extensions \ + python3-colcon-mixin \ + python3-rosdep \ + python3-vcstool \ + && rm -rf /var/lib/apt/lists/* + +# Set the ROS distro to Humble +ENV ROS_DISTRO=humble + +# Initialize rosdep and update for the current ROS distro +RUN rosdep init && \ + rosdep update --rosdistro $ROS_DISTRO + +# Add and update colcon mixins and metadata +RUN colcon mixin add default \ + https://raw.githubusercontent.com/colcon/colcon-mixin-repository/master/index.yaml && \ + colcon mixin update && \ + colcon metadata add default \ + https://raw.githubusercontent.com/colcon/colcon-metadata-repository/master/index.yaml && \ + colcon metadata update + +# Install ROS2 base packages specific to Humble distribution +RUN apt-get update && apt-get install -y --no-install-recommends \ + ros-humble-ros-base=0.10.0-1* \ + && rm -rf /var/lib/apt/lists/* + +# Set environment variables and source ROS2 Humble setup +ENV LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 + +# Source the ROS2 environment automatically when a new shell is created +RUN echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc +RUN echo "source /root/ws/install/setup.bash" >> ~/.bashrc + +RUN mkdir -p /root/ws/src +RUN mkdir -p /root/ws/tmp +COPY . /root/ws/tmp + +WORKDIR /root/ws + +RUN sudo apt update + +# Install dev tools +RUN apt-get update +RUN apt-get install -y curl +RUN curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key | tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc +RUN cat /etc/apt/trusted.gpg.d/apt.llvm.org.asc +RUN echo $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy main\n' \ + $'deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy main\n' \ + $'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main\n' \ + $'deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main\n' \ + | tee -a /etc/apt/sources.list +RUN apt-get update +RUN apt-get install -y clang-format clang-format-19 + +RUN source /opt/ros/$ROS_DISTRO/setup.bash \ + && cd /root/ws \ + && rosdep update \ + && rosdep install --from-paths tmp -r -y + +RUN rm -rf /root/ws/tmp + +RUN source /opt/ros/$ROS_DISTRO/setup.bash \ + && cd /root/ws \ + && colcon build \ + && git clone https://github.com/cornellev/cev_msgs.git src/cev_msgs + +EXPOSE 4567 \ No newline at end of file diff --git a/cev/all/cev_planner_ros2/.devcontainer/devcontainer.json b/cev/all/cev_planner_ros2/.devcontainer/devcontainer.json new file mode 100644 index 0000000..20109ab --- /dev/null +++ b/cev/all/cev_planner_ros2/.devcontainer/devcontainer.json @@ -0,0 +1,51 @@ +{ + "name": "Planner Dev-Container", + "build": { + "dockerfile": "Dockerfile.dev" + }, + "workspaceMount": "source=${localWorkspaceFolder},target=/root/ws/src/ackermann_kf,type=bind", + "workspaceFolder": "/root/ws/src/cev_planner", + "runArgs": [ + "--rm", + "--network=host", + "--ipc=host", + "--privileged" + ], + "containerEnv": { + "DISPLAY": "${localEnv:DISPLAY}" + }, + "mounts": [ + "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached" + ], + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/bin/bash" + } + } + }, + "[cpp]": { + "editor.defaultFormatter": "xaver.clang-format" + }, + "[python]": { + "diffEditor.ignoreTrimWhitespace": false, + "gitlens.codeLens.symbolScopes": [ + "!Module" + ], + "editor.formatOnType": true, + "editor.wordBasedSuggestions": "off", + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "extensions": [ + "ms-iot.vscode-ros", + "ms-vscode.cpptools-extension-pack", + "ms-python.black-formatter", + "xaver.clang-format" + ] + } + }, + "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}" +} \ No newline at end of file diff --git a/cev/all/cev_planner_ros2/.gitignore b/cev/all/cev_planner_ros2/.gitignore new file mode 100644 index 0000000..463fcb4 --- /dev/null +++ b/cev/all/cev_planner_ros2/.gitignore @@ -0,0 +1,6 @@ +.idea/ +build/ +log/ +install/ +cmake-build-debug/ +.vscode/ \ No newline at end of file diff --git a/cev/all/cev_planner_ros2/CMakeLists.txt b/cev/all/cev_planner_ros2/CMakeLists.txt new file mode 100644 index 0000000..3009e8f --- /dev/null +++ b/cev/all/cev_planner_ros2/CMakeLists.txt @@ -0,0 +1,75 @@ +cmake_minimum_required(VERSION 3.10) +project(cev_planner_ros2 LANGUAGES CXX) + +# Set the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(nav_msgs REQUIRED) +find_package(Eigen3 REQUIRED) +find_package(NLopt REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(tf2_geometry_msgs REQUIRED) + +# CEV +find_package(cev_msgs REQUIRED) + +# Add subdirectory for the library +add_subdirectory(cev_planner) + +# Include directories for project headers +include_directories( + include + cev_planner/include +) + +# Source files in the main src folder +set(SOURCES + src/planner_node.cpp +) + +add_executable(planner_node ${SOURCES}) +add_executable(tf_test src/tf_test.cpp) + +ament_target_dependencies(planner_node + rclcpp + cev_msgs + nav_msgs + Eigen3 + tf2_ros + tf2_geometry_msgs +) + +ament_target_dependencies(tf_test + rclcpp + cev_msgs + nav_msgs + Eigen3 + tf2_ros + tf2_geometry_msgs +) + +target_link_libraries(planner_node + cev_planner +) + +target_link_libraries(tf_test + cev_planner +) + +# Declare the executable +install(TARGETS planner_node + DESTINATION lib/${PROJECT_NAME}) + +install(TARGETS tf_test + DESTINATION lib/${PROJECT_NAME}) + +# Install additional resources +install(DIRECTORY launch config + DESTINATION share/${PROJECT_NAME}) + +# Ament package configuration +ament_package() diff --git a/cev/all/cev_planner_ros2/README.md b/cev/all/cev_planner_ros2/README.md new file mode 100644 index 0000000..c8af174 --- /dev/null +++ b/cev/all/cev_planner_ros2/README.md @@ -0,0 +1,17 @@ +### Development Setup + +Make a new folder for the ROS2 workspace +and make a `src` folder inside. `cd` into the +`src` folder and then: +`git clone --recurse-submodules {repo_url}` +`sudo apt install libnlopt-dev` +`sudo apt install libnlopt-cxx-dev` +Clone [cev_msgs](https://github.com/cornellev/cev_msgs) + +Then `cd` back to the workspace root and `colcon build`. + +--- +### Running +In the workspace root: +`source install/setup.bash` +`ros2 launch cev_planner_ros2 launch.py` \ No newline at end of file diff --git a/cev/all/cev_planner_ros2/TODO.md b/cev/all/cev_planner_ros2/TODO.md new file mode 100644 index 0000000..e69de29 diff --git a/cev/all/cev_planner_ros2/cev_planner b/cev/all/cev_planner_ros2/cev_planner new file mode 160000 index 0000000..93b8bd9 --- /dev/null +++ b/cev/all/cev_planner_ros2/cev_planner @@ -0,0 +1 @@ +Subproject commit 93b8bd91735cdebe4d9a945eda9b485cb0a269d0 diff --git a/cev/all/cev_planner_ros2/commit_script.sh b/cev/all/cev_planner_ros2/commit_script.sh new file mode 100755 index 0000000..05f5721 --- /dev/null +++ b/cev/all/cev_planner_ros2/commit_script.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd cev_planner && git add . && git commit -m "$1" && cd .. +git add . && git commit -m "$1" diff --git a/cev/all/cev_planner_ros2/config/.gitkeep b/cev/all/cev_planner_ros2/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cev/all/cev_planner_ros2/config/cev_planner.yaml b/cev/all/cev_planner_ros2/config/cev_planner.yaml new file mode 100644 index 0000000..7fd0c47 --- /dev/null +++ b/cev/all/cev_planner_ros2/config/cev_planner.yaml @@ -0,0 +1,17 @@ +planner_node: + ros__parameters: + x: [-1000.0, 1000.0] + y: [-1000.0, 1000.0] + tau: [-.34, .34] + vel: [-1.0, 1.0] + accel: [-.5, .5] + dtau: [-.20, .20] + +cev_planner_ros2_node: + ros__parameters: + x: [-1000.0, 1000.0] + y: [-1000.0, 1000.0] + tau: [-.34, .34] + vel: [-1.0, 1.0] + accel: [-.5, .5] + dtau: [-.20, .20] \ No newline at end of file diff --git a/cev/all/cev_planner_ros2/config/igvc.yaml b/cev/all/cev_planner_ros2/config/igvc.yaml new file mode 100644 index 0000000..b30127e --- /dev/null +++ b/cev/all/cev_planner_ros2/config/igvc.yaml @@ -0,0 +1,17 @@ +planner_node: + ros__parameters: + x: [-1000.0, 1000.0] + y: [-1000.0, 1000.0] + tau: [-.586, .586] + vel: [-2.2, 2.2] + accel: [-2.5, 2.5] + dtau: [-.785, .785] + +cev_planner_ros2_node: + ros__parameters: + x: [-1000.0, 1000.0] + y: [-1000.0, 1000.0] + tau: [-.586, .586] + vel: [-2.2, 2.2] + accel: [-2.5, 2.5] + dtau: [-.785, .785] diff --git a/cev/all/cev_planner_ros2/costmap.png b/cev/all/cev_planner_ros2/costmap.png new file mode 100644 index 0000000..02b397d Binary files /dev/null and b/cev/all/cev_planner_ros2/costmap.png differ diff --git a/cev/all/cev_planner_ros2/launch/.gitkeep b/cev/all/cev_planner_ros2/launch/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cev/all/cev_planner_ros2/launch/igvc.py b/cev/all/cev_planner_ros2/launch/igvc.py new file mode 100644 index 0000000..3bdcaa7 --- /dev/null +++ b/cev/all/cev_planner_ros2/launch/igvc.py @@ -0,0 +1,25 @@ +from launch import LaunchDescription +from launch_ros.actions import Node +from launch.actions import DeclareLaunchArgument +from ament_index_python.packages import get_package_share_directory +import os + + +def get_path(package, dir, file): + return os.path.join(get_package_share_directory(package), dir, file) + + +def generate_launch_description(): + return LaunchDescription( + [ + Node( + package="cev_planner_ros2", + executable="planner_node", + name="cev_planner_ros2_node", + output="screen", + parameters=[ + get_path("cev_planner_ros2", "config", "igvc.yaml") + ], + ), + ] + ) diff --git a/cev/all/cev_planner_ros2/launch/launch.py b/cev/all/cev_planner_ros2/launch/launch.py new file mode 100644 index 0000000..63e748b --- /dev/null +++ b/cev/all/cev_planner_ros2/launch/launch.py @@ -0,0 +1,25 @@ +from launch import LaunchDescription +from launch_ros.actions import Node +from launch.actions import DeclareLaunchArgument +from ament_index_python.packages import get_package_share_directory +import os + + +def get_path(package, dir, file): + return os.path.join(get_package_share_directory(package), dir, file) + + +def generate_launch_description(): + return LaunchDescription( + [ + Node( + package="cev_planner_ros2", + executable="planner_node", + name="cev_planner_ros2_node", + output="screen", + parameters=[ + get_path("cev_planner_ros2", "config", "cev_planner.yaml") + ], + ), + ] + ) diff --git a/cev/all/cev_planner_ros2/package.xml b/cev/all/cev_planner_ros2/package.xml new file mode 100644 index 0000000..48cd693 --- /dev/null +++ b/cev/all/cev_planner_ros2/package.xml @@ -0,0 +1,28 @@ + + + + cev_planner_ros2 + 0.0.0 + TODO: Package description + sloth + TODO: License declaration + + ament_cmake + + rclcpp + eigen3 + nlopt + tf2 + tf2_ros + tf2_geometry_msgs + + cev_msgs + + + ament_lint_auto + ament_lint_common + + + ament_cmake + + \ No newline at end of file diff --git a/cev/all/cev_planner_ros2/src/planner_node.cpp b/cev/all/cev_planner_ros2/src/planner_node.cpp new file mode 100644 index 0000000..5177915 --- /dev/null +++ b/cev/all/cev_planner_ros2/src/planner_node.cpp @@ -0,0 +1,502 @@ +#include +#include +#include +#include +#include +#include +#include "tf2_ros/transform_listener.h" +#include "tf2_ros/buffer.h" +#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp" +#include "tf2/utils.h" +#include + +#include "local_planning/mpc.h" +#include "global_planning/rrt.h" +#include "cost_map/gaussian_conv.h" +#include "cost_map/nearest.h" +#include "cost_map/nothing.h" + +using namespace cev_planner; + +class PlannerNode : public rclcpp::Node { +public: + PlannerNode(): Node("planner_node"), tf_buffer_(this->get_clock()), tf_listener_(tf_buffer_) { + RCLCPP_INFO(this->get_logger(), "Initializing planner node"); + + Dimensions dimensions = Dimensions{.3, .3, .3}; + // Default values if not run with config file + // Constraints positive_constraints = Constraints{ + // {-1000, 1000}, // x + // {-1000, 1000}, // y + // {-.34, .34}, // tau + // {0, .5}, // vel + // {0, .25}, // accel + // {-.34, .34} // dtau + // }; + + // Default values if not run with config file + Constraints full_constraints = Constraints{ + {-1000.0, 1000.0}, // x + {-1000.0, 1000.0}, // y + {-.34, .34}, // tau + {-1.0, 1.0}, // vel + {-.5, .5}, // accel + {-.20, .20} // dtau + }; + + auto safe_load = [&](auto &target, const char* name){ + try { + this->declare_parameter(name, rclcpp::PARAMETER_DOUBLE_ARRAY); + rclcpp::Parameter param = this->get_parameter(name); + auto v = param.as_double_array(); + target[0] = v[0]; target[1] = v[1]; + } + catch (...) { RCLCPP_WARN(this->get_logger(), "Failed to load %s constraint, using default values (%f, %f).", name, target[0], target[1]); } + }; + + safe_load(full_constraints.x, "x"); + safe_load(full_constraints.y, "y"); + safe_load(full_constraints.tau, "tau"); + safe_load(full_constraints.vel, "vel"); + safe_load(full_constraints.accel, "accel"); + safe_load(full_constraints.dtau, "dtau"); + + + // local_planner = std::make_shared(dimensions, full_constraints, + // std::make_shared(2, .5)); + + local_planner = std::make_shared(dimensions, full_constraints); + global_planner = std::make_shared(dimensions, full_constraints); + + map_sub = this->create_subscription("map", 1, + std::bind(&PlannerNode::map_callback, this, std::placeholders::_1)); + + odom_sub = this->create_subscription("/odometry/filtered", 1, + std::bind(&PlannerNode::odom_callback, this, std::placeholders::_1)); + + target_sub = this->create_subscription("target", 1, + std::bind(&PlannerNode::target_callback, this, std::placeholders::_1)); + + path_pub = this->create_publisher("trajectory", 1); + + // Global plan every 2 seconds + // global_plan_timer = this->create_wall_timer(std::chrono::milliseconds(2000), + // std::bind(&PlannerNode::global_plan_callback, this)); + + // RVIZ Debug + global_path_pub = this->create_publisher("global_path", 1); + local_path_pub = this->create_publisher("local_path", 1); + + target_rviz_sub = this->create_subscription("goal_pose", 1, + std::bind(&PlannerNode::rviz_target_callback, this, std::placeholders::_1)); + } + +private: + Grid grid = Grid(); + State start = State(); + State prev_start = State(); + State target = State(); + + cost_map::NearestGenerator local_plan_cost_generator = cost_map::NearestGenerator(2, .5); + cost_map::Nothing global_plan_cost_generator = cost_map::Nothing(1, .5); + + std::shared_ptr local_plan_cost; + std::shared_ptr global_plan_cost; + bool cost_map_initialized = false; + + bool map_initialized = false; + bool odom_initialized = false; + bool target_initialized = false; + + // Global Planner + std::shared_ptr global_planner; + Trajectory global_path; + double global_path_cost = 100000000; + bool global_path_initialized = false; + + // Local Planner + bool second_iteration_passed = false; + float prev_path_cost = 100000000; + int current_waypoint_in_global = 0; + Trajectory last_path = Trajectory(); + + std::shared_ptr local_planner; + + //// ROS + // TF + tf2_ros::Buffer tf_buffer_; + tf2_ros::TransformListener tf_listener_; + + // Init frames odom map baselink so that we can localize base_link inside map given the odom to + // map transform + + // Listener to the map + rclcpp::Subscription::SharedPtr map_sub; + + // Listener to odom + rclcpp::Subscription::SharedPtr odom_sub; + + // Listener to the target + rclcpp::Subscription::SharedPtr target_sub; + + // Publisher for the planned path + rclcpp::Publisher::SharedPtr path_pub; + + // Wall timer for global plan + // rclcpp::TimerBase::SharedPtr global_plan_timer; + + // RVIZ + rclcpp::Publisher::SharedPtr global_path_pub; + rclcpp::Publisher::SharedPtr local_path_pub; + rclcpp::Subscription::SharedPtr target_rviz_sub; + + float avg_costmap_time = 0; + int cost_map_iters = 0; + float avg_planning_time = 0; + int planning_iters = 0; + + std::chrono::_V2::system_clock::time_point start_time = + std::chrono::high_resolution_clock::now(); + // ------------------------------- + + void global_plan_callback() { + std::cout << "I am attempting a global plan." << std::endl; + + std::optional optional = global_planner->plan_path(grid, start, target); + + if (!optional.has_value()) { + std::cout << "Global Path Failed." << std::endl; + global_path_initialized = false; + } else { + std::cout << "Global Path Planned." << std::endl; + global_path.waypoints = optional.value().waypoints; + + // Publish global path + nav_msgs::msg::Path global_nav_path; + global_nav_path.header.stamp = this->now(); + global_nav_path.header.frame_id = "map"; + global_nav_path.poses.clear(); + + for (State waypoint: global_path.waypoints) { + geometry_msgs::msg::PoseStamped pose; + pose.pose.position.x = waypoint.pose.x; + pose.pose.position.y = waypoint.pose.y; + pose.pose.position.z = 0; + pose.pose.orientation = tf2::toMsg(tf2::Quaternion(tf2::Vector3(0, 0, 1), + waypoint.pose.theta)); + global_nav_path.poses.push_back(pose); + } + + global_path_pub->publish(global_nav_path); + + // cev_msgs::msg::Trajectory current_plan; + + // current_plan.header.stamp = this->now(); + // current_plan.header.frame_id = "map"; + // current_plan.waypoints.clear(); + // current_plan.timestep = global_path.timestep; + + // // std::cout << "What 3" << std::endl; + + // for (State waypoint: global_path.waypoints) { + // cev_msgs::msg::Waypoint msg; + // msg.x = waypoint.pose.x; + // msg.y = waypoint.pose.y; + + // // if (std::abs(waypoint.vel) < .3 + // // && std::abs(waypoint.vel) > 0.0) { // Car cannot move that slow lol + // // waypoint.vel = (waypoint.vel >= 0) ? .3 : -.3; + // // } + + // msg.v = waypoint.vel; + // msg.theta = waypoint.pose.theta; + // msg.tau = waypoint.tau; + // current_plan.waypoints.push_back(msg); + // } + + // std::cout << "Publishing path" << std::endl; + + // path_pub->publish(current_plan); + + global_path_initialized = true; + } + } + + bool passed_waypoint(State state, State waypoint, State prev_waypoint, State next_waypoint, + bool last_waypoint) { + float dot; + + if (last_waypoint) { + dot = (state.pose.x - waypoint.pose.x) * (waypoint.pose.x - prev_waypoint.pose.x) + + (state.pose.y - waypoint.pose.y) * (waypoint.pose.y - prev_waypoint.pose.y); + } else { + dot = (state.pose.x - waypoint.pose.x) * (next_waypoint.pose.x - waypoint.pose.x) + + (state.pose.y - waypoint.pose.y) * (next_waypoint.pose.y - waypoint.pose.y); + } + + float dist = state.pose.distance_to(waypoint.pose); + + return dist < .5 || (dist < .7 && dot > 0); + } + + bool hits_obstacle(Trajectory path) { + for (State waypoint: path.waypoints) { + if (global_plan_cost->cost(waypoint) > .5) { + return true; + } + } + + return false; + } + + void odom_callback(const nav_msgs::msg::Odometry::SharedPtr msg) { + // Transform into map frame + geometry_msgs::msg::TransformStamped transform; + + geometry_msgs::msg::PoseStamped base_link_pose; + base_link_pose.header = msg->header; + base_link_pose.pose = msg->pose.pose; + + geometry_msgs::msg::PoseStamped map_pose; + + try { + transform = tf_buffer_.lookupTransform("map", "odom", tf2::TimePointZero); + tf2::doTransform(base_link_pose, map_pose, transform); + } catch (const tf2::TransformException& ex) { + RCLCPP_DEBUG(this->get_logger(), "Could not transform odom to map: %s", ex.what()); + return; + } + + float qw = map_pose.pose.orientation.w; + float qx = map_pose.pose.orientation.x; + float qy = map_pose.pose.orientation.y; + float qz = map_pose.pose.orientation.z; + + float yaw = restrict_angle(atan2(2.0 * (qw * qz + qx * qy), + 1.0 - 2.0 * (qy * qy + qz * qz))); + + start = State{ + map_pose.pose.position.x, map_pose.pose.position.y, yaw, msg->twist.twist.linear.x, 0}; + + odom_initialized = true; + + // Plan path + if (map_initialized && odom_initialized && target_initialized && !global_path_initialized) { + // std::cout << "Planning" << std::endl; + global_plan_callback(); + } else { + // std::cout << "Map: " << map_initialized << std::endl; + // std::cout << "Odom: " << odom_initialized << std::endl; + // std::cout << "Target: " << target_initialized << std::endl; + // std::cout << "Global Path: " << global_path_initialized << std::endl; + } + + // float moved_dist = start.pose.distance_to(prev_start.pose); + + if (map_initialized && odom_initialized && target_initialized && global_path_initialized) { + // std::cout << "I am entering the local planning loop." << std::endl; + float dist = + start.pose.distance_to(global_path.waypoints[current_waypoint_in_global].pose); + + float dist_to_dest = start.pose.distance_to(target.pose); + + bool passed = passed_waypoint(start, target, + global_path.waypoints[global_path.waypoints.size() - 2], State(), true); + + if (dist_to_dest < .3) { // Reached destination + // Write a trajectory with 0 velocity + cev_msgs::msg::Trajectory current_plan; + + current_plan.header.stamp = msg->header.stamp; + current_plan.header.frame_id = "map"; + current_plan.waypoints.clear(); + + cev_msgs::msg::Waypoint msg; + msg.x = target.pose.x; + msg.y = target.pose.y; + msg.v = 0; + msg.theta = target.pose.theta; + msg.tau = 0; + current_plan.waypoints.push_back(msg); + + path_pub->publish(current_plan); + + target_initialized = false; + + std::cout << "Passed target." << std::endl; + + return; + } + + // std::cout << "I did not pass the target" << std::endl; + + while (dist < .75 + && current_waypoint_in_global + < global_path.waypoints.size()) { // Progress waypoint + current_waypoint_in_global += 1; + prev_path_cost = 100000000; // Reset plan costs for new waypoint + dist = + start.pose.distance_to(global_path.waypoints[current_waypoint_in_global].pose); + } + + // Give next two waypoints to target for planner + Trajectory waypoints; + + if (current_waypoint_in_global < global_path.waypoints.size() - 1) { + waypoints.waypoints.push_back(global_path.waypoints[current_waypoint_in_global]); + + if ((current_waypoint_in_global + 1) < global_path.waypoints.size()) { + waypoints.waypoints.push_back( + global_path.waypoints[current_waypoint_in_global + 1]); + } else { + waypoints.waypoints.push_back(target); + } + } else { + waypoints.waypoints.push_back(target); + } + + // Fast forward last path to current position + // for (int i = 0; i < last_path.waypoints.size(); i++) { + // float dist_to_waypoint = start.pose.distance_to(last_path.waypoints[i].pose); + // if (dist_to_waypoint < .5) { + // last_path.waypoints.erase(last_path.waypoints.begin(), + // last_path.waypoints.begin() + i); + // break; + // } + // } + + // std::cout << "Planning local path" << std::endl; + Trajectory path = local_planner->plan_path(grid, start, target, waypoints, last_path, + local_plan_cost); + + if (path.cost >= prev_path_cost + || hits_obstacle(path)) { // Worse path and hasn't targeted next waypoint + // std::cout << "Worse path" << std::endl; + return; + } + + std::chrono::_V2::system_clock::time_point end_time = + std::chrono::high_resolution_clock::now(); + + avg_planning_time = std::chrono::duration_cast(end_time + - start_time) + .count(); + + std::cout << "Planning Update Time: " << avg_planning_time << "ms" << std::endl; + + start_time = std::chrono::high_resolution_clock::now(); + // std::cout << "Better path" << std::endl; + + last_path = path; + + prev_path_cost = path.cost; + second_iteration_passed = true; + prev_start = start; + + cev_msgs::msg::Trajectory current_plan; + + current_plan.header.stamp = msg->header.stamp; + current_plan.header.frame_id = "map"; + current_plan.waypoints.clear(); + current_plan.timestep = path.timestep; + + for (State waypoint: path.waypoints) { + cev_msgs::msg::Waypoint msg; + msg.x = waypoint.pose.x; + msg.y = waypoint.pose.y; + msg.v = waypoint.vel; + msg.theta = waypoint.pose.theta; + msg.tau = waypoint.tau; + current_plan.waypoints.push_back(msg); + } + + path_pub->publish(current_plan); + + // Publish local path + nav_msgs::msg::Path nav_path; + nav_path.header.stamp = msg->header.stamp; + nav_path.header.frame_id = "map"; + nav_path.poses.clear(); + + for (State waypoint: path.waypoints) { + geometry_msgs::msg::PoseStamped pose; + pose.pose.position.x = waypoint.pose.x; + pose.pose.position.y = waypoint.pose.y; + pose.pose.position.z = 0; + pose.pose.orientation = tf2::toMsg(tf2::Quaternion(tf2::Vector3(0, 0, 1), + waypoint.pose.theta)); + nav_path.poses.push_back(pose); + } + + local_path_pub->publish(nav_path); + } + } + + void map_callback(const nav_msgs::msg::OccupancyGrid::SharedPtr msg) { + grid = Grid(); + grid.origin = Pose{msg->info.origin.position.x, msg->info.origin.position.y, 0}; + grid.resolution = msg->info.resolution; + + grid.data = Eigen::MatrixXf(msg->info.width, msg->info.height); + + for (int i = 0; i < msg->info.width; i++) { + for (int j = 0; j < msg->info.height; j++) { + // Divide by 100 to get probability of occupancy in the range [0, 1] + if (msg->data[j * msg->info.width + i] < 0) { + grid.data(i, j) = -1.0; + } else if (msg->data[j * msg->info.width + i] < 50) { + grid.data(i, j) = 0.0; + } else { + // grid.data(i, j) = std::min(msg->data[j * msg->info.width + i] / 100.0, 1.0); + grid.data(i, j) = 1.0; + } + } + } + + map_initialized = true; + + // auto start_time = std::chrono::high_resolution_clock::now(); + local_plan_cost = local_plan_cost_generator.generate_cost_map(grid); + global_plan_cost = global_plan_cost_generator.generate_cost_map(grid); + // auto end_time = std::chrono::high_resolution_clock::now(); + // avg_costmap_time += + // std::chrono::duration_cast(end_time - start_time).count(); + // cost_map_iters += 1; + + // if (cost_map_iters > 100) { + // std::cout << "Average cost map generation time: " << avg_costmap_time / + // cost_map_iters + // << "ms" << std::endl; + // avg_costmap_time = 0; + // cost_map_iters = 0; + // } + } + + void target_callback(const cev_msgs::msg::Waypoint msg) { + current_waypoint_in_global = 0; + global_path_initialized = false; + target = State{msg.x, msg.y, 0, msg.v, 0}; + prev_path_cost = 100000000; + second_iteration_passed = false; + target_initialized = true; + } + + void rviz_target_callback(const geometry_msgs::msg::PoseStamped msg) { + std::cout << "I received a target." << std::endl; + + current_waypoint_in_global = 0; + global_path_initialized = false; + target = State{msg.pose.position.x, msg.pose.position.y, 0, 0, 0}; + prev_path_cost = 100000000; + second_iteration_passed = false; + target_initialized = true; + } +}; + +int main(int argc, char** argv) { + rclcpp::init(argc, argv); + auto node = std::make_shared(); + rclcpp::spin(node); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/cev/all/cev_planner_ros2/src/tf_test.cpp b/cev/all/cev_planner_ros2/src/tf_test.cpp new file mode 100644 index 0000000..828fbcb --- /dev/null +++ b/cev/all/cev_planner_ros2/src/tf_test.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include + +class OdomToMapTransformer : public rclcpp::Node { +public: + OdomToMapTransformer() + : Node("odom_to_map_transformer"), tf_buffer_(this->get_clock()), tf_listener_(tf_buffer_) { + RCLCPP_INFO(this->get_logger(), "starting..."); + odom_sub_ = this->create_subscription("/odometry/filtered", 1, + std::bind(&OdomToMapTransformer::odom_callback, this, std::placeholders::_1)); + + transformed_pose_pub_ = + this->create_publisher("/base_link_in_map", 1); + } + +private: + void odom_callback(const nav_msgs::msg::Odometry::SharedPtr msg) { + geometry_msgs::msg::PoseStamped base_link_pose; + base_link_pose.header = msg->header; + base_link_pose.pose = msg->pose.pose; + + // Transform from "odom" to "map" + try { + geometry_msgs::msg::TransformStamped transform_stamped = + tf_buffer_.lookupTransform("map", "odom", tf2::TimePointZero); + + geometry_msgs::msg::PoseStamped map_pose; + tf2::doTransform(base_link_pose, map_pose, transform_stamped); + + // Publish transformed pose in "map" frame + transformed_pose_pub_->publish(map_pose); + RCLCPP_INFO(this->get_logger(), "Published transformed pose in map frame"); + } catch (const tf2::TransformException& ex) { + RCLCPP_WARN(this->get_logger(), "Could not transform odom to map: %s", ex.what()); + } + } + + rclcpp::Subscription::SharedPtr odom_sub_; + rclcpp::Publisher::SharedPtr transformed_pose_pub_; + tf2_ros::Buffer tf_buffer_; + tf2_ros::TransformListener tf_listener_; +}; + +int main(int argc, char** argv) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/cev/all/cev_planner_ros2/tmp/Makefile b/cev/all/cev_planner_ros2/tmp/Makefile new file mode 100644 index 0000000..a2f7d4c --- /dev/null +++ b/cev/all/cev_planner_ros2/tmp/Makefile @@ -0,0 +1,18 @@ +all: build run + +max: maxbuild run + +build: chat.cpp + g++ -std=c++17 chat.cpp -I/usr/include/python3.10 -lpython3.10 -lnlopt -o path_planner + +maxbuild: chat.cpp + g++ -std=c++17 chat.cpp -I/usr/include/python3.10 -lpython3.10 -lnlopt -O3 -o path_planner + +kernel: kernel.cpp + g++ -std=c++17 -O3 -I/usr/include/eigen3 kernel.cpp -o kernel `pkg-config --cflags --libs opencv4` + +clean: ./path_planner + rm -f path_planner + +run: ./path_planner + ./path_planner diff --git a/cev/all/cev_planner_ros2/tmp/chat.cpp b/cev/all/cev_planner_ros2/tmp/chat.cpp new file mode 100644 index 0000000..36076a6 --- /dev/null +++ b/cev/all/cev_planner_ros2/tmp/chat.cpp @@ -0,0 +1,443 @@ +#include +#include +#include +#include +#include +#include +#include "matplotlib-cpp/matplotlibcpp.h" +namespace plt = matplotlibcpp; + +struct Dimensions { + double wheelbase; + double width; + double length; +}; + +// Define the bounds of the state space (min, max) +struct Bounds { + double x[2]; + double y[2]; + double tau[2]; + double vel[2]; + double accel[2]; + double dtau[2]; +}; + +struct State { + double x; + double y; + double theta; // Heading + double tau; // Steering angle + double vel; // Velocity +}; + +// Update step +struct Input { + double tau; + double vel; +}; + +double bound(double x, double bound[2]) { + return std::clamp(x, bound[0], bound[1]); +} + +/** + * @brief Return a `State` updated with `Input u` + * + * @return `State x` updated with `Input u` and bounded by `Bounds bounds` over timestep `dt` + */ +State update(State& x, Input& u, Bounds& bounds, double dt, Dimensions& dims) { + State _x; + + double accel = bound(u.vel - x.vel, bounds.accel); + double dtau = bound(u.tau - x.tau, bounds.dtau); + + _x.vel = bound(x.vel + accel * dt, bounds.vel); + _x.tau = bound(x.tau + dtau * dt, bounds.tau); + + double avg_vel = (x.vel + _x.vel) / 2; + + double R = dims.wheelbase / tan(x.tau); + double dtheta = (avg_vel / R) * dt; + _x.theta = x.theta + dtheta; + + double avg_theta = (x.theta + _x.theta) / 2; + double avg_tau = (x.tau + _x.tau) / 2; + + _x.x = bound(x.x + avg_vel * cos(avg_theta + avg_tau) * dt, bounds.x); + _x.y = bound(x.y + avg_vel * sin(avg_theta + avg_tau) * dt, bounds.y); + + return _x; +} + +std::string to_string(State& x) { + return ( + "State(\n" + " x: " + std::to_string(x.x) + "\n" + " y: " + std::to_string(x.y) + "\n" + " theta: " + std::to_string(x.theta) + "\n" + " tau: " + std::to_string(x.tau) + "\n" + " vel: " + std::to_string(x.vel) + "\n" + ")" + ); +} + +// INITIALIZE AS IF CLASS +std::vector waypoints = { + {-1.0, -1.0, 0, 0, 0}, {1.0, 2.0, 0, 0, 0}, {10.0, 10.0, 0, 0, 0}}; // Waypoints +// std::vector> obstacles = {{1, 1}, {1, 2}, {2, 2}}; // Obstacle coordinates + +// 10 obstacles randomly placed +std::vector> obstacles = {{1, 1}, {1, 2}, {2, 1}, {3, 4}, {4, 6.5}, {4, 7.5}, + {4, 8.5}, {4, 9.5}, {4, 5}, {5, 4}, {6, 4}, {7, 8}, {8, 7}, {9, 10}}; + +// std::vector> obstacles = {}; + +std::vector path = {waypoints[0]}; + +Bounds bounds = { + {-10, 10}, // x + {-10, 10}, // y + {-M_PI / 4, M_PI / 4}, // tau + {0, 10}, // vel + {-2, 2}, // accel + {-M_PI / 8, M_PI / 8} // dtau +}; +Dimensions dims = { + .2, // wheelbase + 1, // width + 1 // length +}; +double dt = .1; + +double distance(State a, State b) { + double dx = a.x - b.x; + double dy = a.y - b.y; + return sqrt(dx * dx + dy * dy); +} + +double distance(State a, std::vector b) { + double dx = a.x - b[0]; + double dy = a.y - b[1]; + return sqrt(dx * dx + dy * dy); +} + +double distance(const std::vector& a, const std::vector& b) { + double dx = a[0] - b[0]; + double dy = a[1] - b[1]; + return sqrt(dx * dx + dy * dy); +} + +double path_obs_cost(std::vector& path, std::vector>& obstacles) { + double cost = 0; + + for (int i = 0; i < path.size(); i++) { + for (int j = 0; j < obstacles.size(); j++) { + cost += 3 / distance(path[i], obstacles[j]); + // std::cout << 1 / distance({path[i].x, path[i].y}, obstacles[j]) << std::endl; + } + } + return cost; +} + +double path_waypoints_cost(std::vector& path, std::vector& waypoints) { + double cost = 0; + for (int i = 1; i < path.size(); i++) { + // Waypoints + for (int j = 1; j < waypoints.size() - 1; j++) { + cost += .3 * distance(path[i], waypoints[j]); + } + // Goal + cost += 2 * distance(path[i], waypoints[waypoints.size() - 1]); + } + return cost; +} + +std::vector decompose(State start_state, std::vector u, Bounds& bounds, double dt, + Dimensions& dims) { + std::vector path; + State state = start_state; + path.push_back(state); + for (int i = 0; i < u.size(); i += 2) { + Input input = {u[i], u[i + 1]}; + state = update(state, input, bounds, dt, dims); + path.push_back(state); + } + return path; +} + +// Objective function +double objective_function(const std::vector& x, std::vector& grad, void* data) { + std::vector path_ = decompose(path[path.size() - 1], x, bounds, dt, dims); + double cost = path_obs_cost(path_, obstacles) + path_waypoints_cost(path_, waypoints); + + // std::cout << "Cost: " << cost << std::endl; + // Path obs + // std::cout << "Path obs cost: " << path_obs_cost(path, obstacles) << std::endl; + // Path waypoints + // std::cout << "Path waypoints cost: " << path_waypoints_cost(path, waypoints) << std::endl; + + return cost; +} + +void test_state() { + // TEST STATE + State state = { + 0, // x + 0, // y + M_PI / 4, // theta + 0, // tau + 0 // vel + }; + Input input = { + 0, // dtau + -2 // vel + }; + Bounds bounds = { + {-10, 10}, // x + {-10, 10}, // y + {-M_PI / 4, M_PI / 4}, // tau + {-10, 10}, // vel + {-2.0, 2.0}, // accel + {-M_PI / 8, M_PI / 8} // dtau + }; + Dimensions dims = { + .2, // wheelbase + 1, // width + 1 // length + }; + + // Start time measurement + auto start_time = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < 30; i++) { + state = update(state, input, bounds, .1, dims); + } + + // End time measurement + auto end_time = std::chrono::high_resolution_clock::now(); + + auto elapsed = end_time - start_time; + + std::cout << to_string(state) << std::endl; + std::cout << "State update ran for " << elapsed.count() << " seconds." << std::endl; + + std::cout << "Goal: " << waypoints[1].x << ", " << waypoints[1].y << std::endl; + std::cout << "\n Distance to Goal: " << distance({state.x, state.y}, waypoints[1]) << std::endl; +} + +void plot_path(const std::vector& path, const std::vector& waypoints, + const std::vector>& obstacles, double obstacle_radius = 0.3) { + std::vector path_x, path_y; + for (const auto& state: path) { + path_x.push_back(state.x); + path_y.push_back(state.y); + } + + std::vector wp_x, wp_y; + for (const auto& wp: waypoints) { + wp_x.push_back(wp.x); + wp_y.push_back(wp.y); + } + + // Plot path + plt::figure_size(800, 600); + plt::plot(path_x, path_y, + {{"label", "Path"}, {"color", "blue"}, {"linestyle", "-"}, {"marker", "o"}}); + + // Plot waypoints + plt::scatter(wp_x, wp_y, 100, {{"label", "Waypoints"}, {"color", "green"}}); + + // Draw obstacles as circles + for (const auto& obs: obstacles) { + std::vector circle_x, circle_y; + for (double angle = 0; angle <= 2 * M_PI; angle += 0.1) { + circle_x.push_back(obs[0] + obstacle_radius * cos(angle)); + circle_y.push_back(obs[1] + obstacle_radius * sin(angle)); + } + plt::plot(circle_x, circle_y, {{"color", "red"}, {"label", "Obstacle"}}); + } + + // Add legend (avoiding duplicate obstacle labels) + plt::legend(); + plt::title("Path Planning Visualization"); + plt::xlabel("X Position"); + plt::ylabel("Y Position"); + plt::grid(true); + plt::axis("equal"); // Maintain aspect ratio + + plt::show(); +} + +void optimize_iter(nlopt::opt& opt, std::vector& initial_input) { + double minf; + nlopt::result result = opt.optimize(initial_input, minf); +} + +void plan() { + int num_states = 10; + + // nlopt::opt opt(nlopt::LN_COBYLA, num_states * 2); + // nlopt::opt opt(nlopt::LN_BOBYQA, num_states * 2); + nlopt::opt opt(nlopt::LN_NELDERMEAD, num_states * 2); + // nlopt::opt opt(nlopt::LN_SBPLX, num_states * 2); + // nlopt::opt opt(nlopt::LN_PRAXIS, num_states * 2); + opt.set_min_objective(objective_function, nullptr); + opt.set_xtol_rel(1e-4); + + // Set bounds + std::vector lb, ub; + // for (int i = 0; i < num_states; i++) { + // lb.push_back(-M_PI / 8); + // lb.push_back(-2); + // ub.push_back(M_PI / 8); + // ub.push_back(2); + // } + for (int i = 0; i < num_states; i++) { + lb.push_back(-M_PI / 4); + lb.push_back(0); + ub.push_back(M_PI / 4); + ub.push_back(10); + } + // opt.set_lower_bounds(lb); + // opt.set_upper_bounds(ub); + + std::vector x = {}; + for (int i = 0; i < num_states; i++) { + x.push_back(0); + x.push_back(0); + } + + std::vector time_per_iter = {}; + + // std::vector path = {waypoints[0]}; + + // Start time measurement + auto start_time = std::chrono::high_resolution_clock::now(); + auto mid_time = std::chrono::high_resolution_clock::now(); + auto end_time = std::chrono::high_resolution_clock::now(); + + double max_time = 0; + + int num_iters = 0; + + while (distance(path[path.size() - 1], waypoints[waypoints.size() - 1]) > 0.5) { + mid_time = std::chrono::high_resolution_clock::now(); + optimize_iter(opt, x); + num_iters++; + end_time = std::chrono::high_resolution_clock::now(); + + if (std::chrono::duration(end_time - mid_time).count() > max_time) { + max_time = std::chrono::duration(end_time - mid_time).count(); + } + + time_per_iter.push_back(std::chrono::duration(end_time - mid_time).count()); + std::cout << "Iteration time: " + << std::chrono::duration(end_time - mid_time).count() << std::endl; + + std::vector new_path = decompose(path[path.size() - 1], x, bounds, dt, dims); + // Keep only the first 2 states of the new path + path.push_back(new_path[1]); + path.push_back(new_path[2]); + + // Print cost + // std::cout << "Cost: " << objective_function(x, x, nullptr) << std::endl; + + // Shift all inititial inputs left by two input sets + // x.erase(x.begin(), x.begin() + 4); + // // // Add two new input sets to the end + // x.push_back(0); + // x.push_back(0); + // x.push_back(0); + // x.push_back(0); + + // std::cout << "Inputs: " << std::endl; + // for (int i = 0; i < x.size(); i += 2) { + // std::cout << "dtau: " << x[i] << ", accel: " << x[i + 1] << std::endl; + // } + + // Set all x to 0 + x.clear(); + for (int i = 0; i < num_states; i++) { + x.push_back(0); + // Push back final velocity + x.push_back(path[path.size() - 1].vel); + } + + // plot_path(path, waypoints, obstacles); + } + + end_time = std::chrono::high_resolution_clock::now(); + + std::cout << "Max iteration time: " << max_time << std::endl; + std::cout << "Avg iteration time: " + << std::chrono::duration(end_time - start_time).count() / num_iters + << std::endl; + + // std::cout << "Time Per iter: " << std::endl; + // for (int i = 0; i < time_per_iter.size(); i++) { + // std::cout << time_per_iter[i] << std::endl; + // } + + plot_path(path, waypoints, obstacles); +} + +void optimize() { + int num_states = 10; + + nlopt::opt opt(nlopt::LN_COBYLA, num_states * 2); + opt.set_min_objective(objective_function, nullptr); + opt.set_xtol_rel(1e-4); + + // Set maximum time limit to 3 seconds + // opt.set_maxtime(3.0); + + std::vector x = {}; + for (int i = 0; i < num_states; i++) { + x.push_back(0); + x.push_back(0); + } + double minf; + + // Start time measurement + auto end_time = std::chrono::high_resolution_clock::now(); + auto start_time = std::chrono::high_resolution_clock::now(); + + try { + nlopt::result result = opt.optimize(x, minf); + end_time = std::chrono::high_resolution_clock::now(); + std::cout << "Optimization completed successfully." << std::endl; + } catch (std::exception& e) { + std::cerr << "Optimization failed: " << e.what() << std::endl; + } + + // End time measurement + std::chrono::duration elapsed = end_time - start_time; + + std::cout << "Optimization ran for " << elapsed.count() << " seconds." << std::endl; + std::cout << "\nCOST: " << minf << "\n" << std::endl; + + std::cout << "Inputs List: " << std::endl; + for (int i = 0; i < x.size(); i += 2) { + std::cout << "dtau: " << x[i] << ", accel: " << x[i + 1] << std::endl; + } + + std::cout << "States Table: " << std::endl; + std::vector path = decompose(waypoints[0], x, bounds, dt, dims); + // Headers with tabs + printf("x\ty\ttheta\ttau\tvel\n"); + + // Print table using tabs inside printf instead + for (int i = 0; i < path.size(); i++) { + printf("%.3f\t%.3f\t%.3f\t%.3f\t%.3f\n", path[i].x, path[i].y, path[i].theta, path[i].tau, + path[i].vel); + } + + plot_path(path, waypoints, obstacles); +} + +int main() { + // optimize(); + plan(); + return 0; +} diff --git a/cev/all/cev_planner_ros2/tmp/costmap.png b/cev/all/cev_planner_ros2/tmp/costmap.png new file mode 100644 index 0000000..308eb54 Binary files /dev/null and b/cev/all/cev_planner_ros2/tmp/costmap.png differ diff --git a/cev/all/cev_planner_ros2/tmp/kernel b/cev/all/cev_planner_ros2/tmp/kernel new file mode 100755 index 0000000..106e38a Binary files /dev/null and b/cev/all/cev_planner_ros2/tmp/kernel differ diff --git a/cev/all/cev_planner_ros2/tmp/kernel.cpp b/cev/all/cev_planner_ros2/tmp/kernel.cpp new file mode 100644 index 0000000..38692f1 --- /dev/null +++ b/cev/all/cev_planner_ros2/tmp/kernel.cpp @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include // Include OpenCV header +#include +#include +#include + +using namespace Eigen; +using namespace std; + +VectorXf createGaussianKernel(int search_radius, float sigma) { + int kernel_size = 2 * search_radius + 1; + VectorXf kernel(kernel_size); + float sum = 0.0f; + + for (int i = 0; i < kernel_size; ++i) { + int dist = i - search_radius; + kernel(i) = exp(-(dist * dist) / (2 * sigma * sigma)); + sum += kernel(i); + } + + // Normalize the kernel to ensure the sum is 1 + kernel /= sum; + return kernel; +} + +void visualizeCostmap(const std::vector>& costmap, const MatrixXf& grid, + const std::string& filename = "costmap.png") { + int rows = costmap.size(); + int cols = costmap[0].size(); + + // Flatten the 2D vector into a 1D array for cv::Mat + std::vector flatCostmap; + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < cols; ++j) { + // Normalize cost values to range [0, 255] and add them to flatCostmap + flatCostmap.push_back(static_cast(costmap[i][j] + * 255.0)); // Assuming 0 <= cost <= 1 + // cout << costmap[i][j] << endl; + } + } + + // Create a cv::Mat from the flattened 1D array + cv::Mat image(rows, cols, CV_8UC1, flatCostmap.data()); // 8-bit single-channel image + + // Apply a colormap (e.g., 'JET', 'HOT', 'PLASMA') + cv::Mat coloredImage; + cv::applyColorMap(image, coloredImage, cv::COLORMAP_HOT); + + // Overlay the original grid on the colored image + for (int i = 0; i < grid.rows(); ++i) { + for (int j = 0; j < grid.cols(); ++j) { + if (grid(i, j) > 0) { + // cv::circle(coloredImage, cv::Point(j, i), 1, cv::Scalar(200, 200, 200), -1); + cv::rectangle(coloredImage, cv::Point(j, i), cv::Point(j + 1, i + 1), + cv::Scalar(200, 200, 200), -1); + } + } + } + + // Invert the image (optional) + // cv::bitwise_not(coloredImage, coloredImage); + + // Make the red stronger + // cv::Mat channels[3]; + // cv::split(coloredImage, channels); + // channels[2] = channels[2] * 2.0; + // cv::merge(channels, 3, coloredImage); + + // Save the colored image to a file + cv::imwrite(filename, coloredImage); + + std::cout << "Costmap image saved as " << filename << std::endl; +} + +// MatrixXf random_dfs(int rows, int cols) { +// MatrixXf grid = MatrixXf::Zero(rows, cols); +// int num_obstacles = 5000; + +// std::unordered_set> y; +// } + +MatrixXf generate_random_obstacles(int rows, int cols) { + int num_obstacles = 100; + int obstacle_radius = 10; + MatrixXf grid = MatrixXf::Zero(rows, cols); + + srand(1000); + + for (int i = 0; i < num_obstacles; ++i) { + int x = rand() % rows; + int y = rand() % cols; + + for (int j = -obstacle_radius; j <= obstacle_radius; ++j) { + for (int k = -obstacle_radius; k <= obstacle_radius; ++k) { + int new_x = x + j; + int new_y = y + k; + + float dist = sqrt(j * j + k * k); + + if (new_x >= 0 && new_x < rows && new_y >= 0 && new_y < cols + && dist <= obstacle_radius) { + grid(new_x, new_y) = 1; + } + } + } + } + + return grid; +} + +int main() { + // Example grid initialization (replace with your actual data) + // MatrixXf grid(5, 5); // Example 5x5 grid + // grid << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + // 24, + // 25; + + // 5x5 grid with random 1s and 0s + MatrixXf grid = generate_random_obstacles(1000, 1000); + grid = (grid.array() > .5).cast(); + + // Print grid + // cout << "Grid:\n" << grid << endl; + + int search_radius = 30; + int kernel_size = 2 * search_radius + 1; + float sigma = 15.0; + + // Kernel initialization + VectorXf kernel = createGaussianKernel(search_radius, sigma); + + auto start_time = chrono::high_resolution_clock::now(); + + // Convolution along rows + MatrixXf row_conv = MatrixXf::Zero(grid.rows(), grid.cols()); + for (int i = 0; i < grid.rows(); ++i) { + for (int j = 0; j < grid.cols(); ++j) { + float sum = 0.0f; + for (int k = -search_radius; k <= search_radius; ++k) { + int idx = j + k; + if (idx >= 0 && idx < grid.cols()) { + sum += grid(i, idx) * kernel(k + search_radius); + } + } + row_conv(i, j) = sum; + } + } + + // Convolution along columns + MatrixXf cost_map = MatrixXf::Zero(grid.rows(), grid.cols()); + for (int j = 0; j < row_conv.cols(); ++j) { + for (int i = 0; i < row_conv.rows(); ++i) { + float sum = 0.0f; + for (int k = -search_radius; k <= search_radius; ++k) { + int idx = i + k; + if (idx >= 0 && idx < row_conv.rows()) { + sum += row_conv(idx, j) * kernel(k + search_radius); + } + } + cost_map(i, j) = sum; + } + } + + auto end_time = chrono::high_resolution_clock::now(); + chrono::duration elapsed = end_time - start_time; + + // Output the cost map + // cout << "Cost Map:\n" << cost_map << endl; + cout << "Elapsed Time: " << elapsed.count() << " seconds" << endl; + + // Visualize the cost map + // vis_cost_map(cost_map); + // Convert cost map to 2D vector for visualization + + cout << cost_map.rows() << endl; + + std::vector> cost_map_vec(cost_map.rows(), + std::vector(cost_map.cols())); + + for (int i = 0; i < cost_map.rows(); ++i) { + for (int j = 0; j < cost_map.cols(); ++j) { + cost_map_vec[i][j] = cost_map(i, j); + } + } + + visualizeCostmap(cost_map_vec, grid, "costmap.png"); + + return 0; +} diff --git a/cev/all/cev_planner_ros2/tmp/matplotlib-cpp b/cev/all/cev_planner_ros2/tmp/matplotlib-cpp new file mode 160000 index 0000000..ef0383f --- /dev/null +++ b/cev/all/cev_planner_ros2/tmp/matplotlib-cpp @@ -0,0 +1 @@ +Subproject commit ef0383f1315d32e0156335e10b82e90b334f6d9f diff --git a/cev/all/cev_planner_ros2/tmp/path_planner b/cev/all/cev_planner_ros2/tmp/path_planner new file mode 100755 index 0000000..199eaa8 Binary files /dev/null and b/cev/all/cev_planner_ros2/tmp/path_planner differ diff --git a/cev/all/cev_planner_ros2/trajectory.png b/cev/all/cev_planner_ros2/trajectory.png new file mode 100644 index 0000000..2596124 Binary files /dev/null and b/cev/all/cev_planner_ros2/trajectory.png differ diff --git a/cev/all/cev_teleop_ros2/CMakeLists.txt b/cev/all/cev_teleop_ros2/CMakeLists.txt new file mode 100644 index 0000000..87fd5be --- /dev/null +++ b/cev/all/cev_teleop_ros2/CMakeLists.txt @@ -0,0 +1,44 @@ + +cmake_minimum_required(VERSION 3.8) +project(teleop) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(ackermann_msgs REQUIRED) + +add_executable(joy_interpreter src/joy_interpreter.cpp) +ament_target_dependencies(joy_interpreter rclcpp sensor_msgs ackermann_msgs) + +install(TARGETS + joy_interpreter + DESTINATION lib/${PROJECT_NAME} +) + +install(DIRECTORY launch/ + DESTINATION share/${PROJECT_NAME}/launch +) + +install(DIRECTORY config/ + DESTINATION share/${PROJECT_NAME}/config +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() + diff --git a/cev/all/cev_teleop_ros2/config/.gitkeep b/cev/all/cev_teleop_ros2/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cev/all/cev_teleop_ros2/launch/launch.py b/cev/all/cev_teleop_ros2/launch/launch.py new file mode 100644 index 0000000..fced3e0 --- /dev/null +++ b/cev/all/cev_teleop_ros2/launch/launch.py @@ -0,0 +1,25 @@ + +from launch import LaunchDescription +from launch_ros.actions import Node +from ament_index_python.packages import get_package_share_directory +import os +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource + + +def generate_launch_description(): + return LaunchDescription( + [ + Node( + package="joy", + executable="joy_node", + name="joy_node", + ), + Node( + package="teleop", + executable="joy_interpreter", + name="joy_interpreter", + ) + ] + ) + diff --git a/cev/all/cev_teleop_ros2/package.xml b/cev/all/cev_teleop_ros2/package.xml new file mode 100644 index 0000000..92322c8 --- /dev/null +++ b/cev/all/cev_teleop_ros2/package.xml @@ -0,0 +1,23 @@ + + + + teleop + 0.0.0 + TODO: Package description + root + TODO: License declaration + + ament_cmake + + joy + rclcpp + sensor_msgs + ackermann_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/cev/all/cev_teleop_ros2/src/joy_interpreter.cpp b/cev/all/cev_teleop_ros2/src/joy_interpreter.cpp new file mode 100644 index 0000000..d03bba1 --- /dev/null +++ b/cev/all/cev_teleop_ros2/src/joy_interpreter.cpp @@ -0,0 +1,103 @@ +#include "rclcpp/rclcpp.hpp" +#include "sensor_msgs/msg/joy.hpp" +#include "ackermann_msgs/msg/ackermann_drive.hpp" +#include +#include + +class LogitechRead { +public: + LogitechRead(const std::vector& axes, const std::vector& buttons) + : axes_(axes), buttons_(buttons) {} + + float get_left_trigger() const { + return 1 - (axes_[2] + 1) / 2; + } + + float get_right_trigger() const { + return 1 - (axes_[5] + 1) / 2; + } + + float get_y_button() const { + return buttons_[3]; + } + + float get_right_stick_x() const { + return -axes_[3]; + } + +private: + std::vector axes_; + std::vector buttons_; +}; + +class JoyInterpreter : public rclcpp::Node { +public: + JoyInterpreter(): Node("joy_interpreter") { + movement_pub_ = + this->create_publisher("rc_movement_msg", 1); + joy_sub_ = this->create_subscription("joy", 1, + std::bind(&JoyInterpreter::joy_to_twist, this, std::placeholders::_1)); + + this->declare_parameter("joy_interpreter/teleop/deadzone", 0.1); + this->declare_parameter("joy_interpreter/teleop/max_turning_angle", + M_PI / 9); // 20 degrees in radians + this->declare_parameter("joy_interpreter/teleop/max_velocity", 1.5); + + deadzone_ = this->get_parameter("joy_interpreter/teleop/deadzone").as_double(); + max_turning_angle_ = + this->get_parameter("joy_interpreter/teleop/max_turning_angle").as_double(); + max_velocity_ = this->get_parameter("joy_interpreter/teleop/max_velocity").as_double(); + + RCLCPP_INFO(this->get_logger(), "Parameter max_turning_angle=%f", max_turning_angle_); + RCLCPP_INFO(this->get_logger(), "Parameter max_velocity=%f", max_velocity_); + RCLCPP_INFO(this->get_logger(), "Parameter deadzone=%f", deadzone_); + } + +private: + void joy_to_twist(const sensor_msgs::msg::Joy::SharedPtr msg) { + LogitechRead gamepad_data(msg->axes, msg->buttons); + + // Calculate turning angle + float turn = -gamepad_data.get_right_stick_x() * max_turning_angle_; + if (std::abs(turn) < deadzone_) { + turn = 0.0; + } + + // Calculate velocity + float drive = 0.0; + +// if (gamepad_data.get_y_button()) { +// drive = .3; +// } else { + drive = (gamepad_data.get_right_trigger() - gamepad_data.get_left_trigger()) + * max_velocity_; +// } + + // float drive = (gamepad_data.get_right_trigger() - gamepad_data.get_left_trigger()) + // * max_velocity_; + + // float drive = gamepad_data.get_y_button() ? .3 : 0.0; + + // RCLCPP_INFO(this->get_logger(), "Button: %f", gamepad_data.get_y_button()); + + // Publish AckermannDrive message + auto ackermann_msg = ackermann_msgs::msg::AckermannDrive(); + ackermann_msg.steering_angle = turn; + ackermann_msg.speed = drive; + movement_pub_->publish(ackermann_msg); + } + + rclcpp::Publisher::SharedPtr movement_pub_; + rclcpp::Subscription::SharedPtr joy_sub_; + + double deadzone_; + double max_turning_angle_; + double max_velocity_; +}; + +int main(int argc, char* argv[]) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/cev/all/cev_trajectory_follower_ros2/CMakeLists.txt b/cev/all/cev_trajectory_follower_ros2/CMakeLists.txt new file mode 100644 index 0000000..2476c72 --- /dev/null +++ b/cev/all/cev_trajectory_follower_ros2/CMakeLists.txt @@ -0,0 +1,68 @@ + +cmake_minimum_required(VERSION 3.8) +project(trajectory_follower) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(ackermann_msgs REQUIRED) +find_package(cev_msgs REQUIRED) +find_package(nav_msgs REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(tf2_geometry_msgs REQUIRED) + + +add_executable(trajectory_follower_node src/trajectory_follower.cpp) +add_executable(debug_trajectory src/debug_trajectory_publish.cpp) + +ament_target_dependencies(trajectory_follower_node + rclcpp + std_msgs + sensor_msgs + ackermann_msgs + cev_msgs + nav_msgs + tf2_ros + tf2_geometry_msgs) + +ament_target_dependencies(debug_trajectory + rclcpp + std_msgs + sensor_msgs + ackermann_msgs + cev_msgs + nav_msgs + tf2_ros + tf2_geometry_msgs) + +install(TARGETS trajectory_follower_node debug_trajectory + DESTINATION lib/${PROJECT_NAME}) + +install(DIRECTORY launch/ + DESTINATION share/${PROJECT_NAME}/launch +) + +install(DIRECTORY config/ + DESTINATION share/${PROJECT_NAME}/config +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() + diff --git a/cev/all/cev_trajectory_follower_ros2/config/.gitkeep b/cev/all/cev_trajectory_follower_ros2/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cev/all/cev_trajectory_follower_ros2/launch/launch.py b/cev/all/cev_trajectory_follower_ros2/launch/launch.py new file mode 100644 index 0000000..e51aca0 --- /dev/null +++ b/cev/all/cev_trajectory_follower_ros2/launch/launch.py @@ -0,0 +1,24 @@ +from launch import LaunchDescription +from launch_ros.actions import Node +from ament_index_python.packages import get_package_share_directory +import os +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource + + +def generate_launch_description(): + return LaunchDescription( + [ + Node( + package="trajectory_follower", + executable="trajectory_follower_node", + name="trajectory_node", + ), + # Node( + # package="controls", + # executable="debug_trajectory", + # name="debug_trajectory", + # ) + ] + ) + diff --git a/cev/all/cev_trajectory_follower_ros2/package.xml b/cev/all/cev_trajectory_follower_ros2/package.xml new file mode 100644 index 0000000..712358a --- /dev/null +++ b/cev/all/cev_trajectory_follower_ros2/package.xml @@ -0,0 +1,28 @@ + + + + trajectory_follower + 0.0.0 + TODO: Package description + sloth + TODO: License declaration + + ament_cmake + + ament_lint_auto + ament_lint_common + + rclcpp + std_msgs + sensor_msgs + ackermann_msgs + cev_msgs + nav_msgs + tf2 + tf2_ros + tf2_geometry_msgs + + + ament_cmake + + diff --git a/cev/all/cev_trajectory_follower_ros2/src/debug_trajectory_publish.cpp b/cev/all/cev_trajectory_follower_ros2/src/debug_trajectory_publish.cpp new file mode 100644 index 0000000..483e1da --- /dev/null +++ b/cev/all/cev_trajectory_follower_ros2/src/debug_trajectory_publish.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include + +using cev_msgs::msg::Trajectory; +using cev_msgs::msg::Waypoint; + +// ROS2 Node that publishes a cev_msgs trajectory +class DebugTrajectoryPublish : public rclcpp::Node { +public: + DebugTrajectoryPublish(): Node("debug_trajectory_publish") { + trajectory_pub_ = this->create_publisher("trajectory", 1); + + timer_ = this->create_wall_timer(std::chrono::milliseconds(2000), + std::bind(&DebugTrajectoryPublish::timer_callback, this)); + } + +private: + rclcpp::Publisher::SharedPtr trajectory_pub_; + rclcpp::TimerBase::SharedPtr timer_; + + std::vector>> trajectories = {// { + // {0.0, 0.0, 0.9}, + // {0.3, 0.1, 0.7}, + // {0.6, 0.2, 0.5}, + // {0.9, 0.3, 0.3}, + // {1.2, 0.4, 0.2}, + // {1.5, 0.5, 0.2}, + // {0.6, 0.5, -0.3}, + // {1.5, -0.5, 0.3} + // }, + // { + // {0.6, 0.2, 0.5}, + // {0.9, 0.3, 0.3}, + // {1.2, 0.4, 0.2}, + // {1.5, 0.5, 0.2}, + // {0.6, 0.5, -0.3}, + // {1.5, -0.5, 0.3}, + // {0.6, 0.5, -0.3}, + // {1.5, -0.5, 0.3}, + // }, + { + // {1.2, 0.4, 0.2}, + // {1.5, 0.5, 0.2}, + // {0.6, 0.5, -0.3}, + // {1.5, -0.5, 0.3}, + // {0.6, 0.5, -0.3}, + // {1.5, -0.5, 0.3}, + // {0.6, 0.5, -0.3}, + // {1.5, -0.5, 0.3}, + // {0.0, 0.0, -.3}, + }, + {{0.0, 0.0, 0.9}, {0.3, 0.1, 0.7}, {0.6, 0.2, 0.5}, {0.9, 0.3, 0.3}, {1.2, 0.4, 0.2}, + {1.5, 0.5, 0.2}, {0.6, 0.5, -0.3}, + // {1.5, -0.5, 0.3}, + {1.5, -0.5, 0.3}, {0.0, 0.0, -.3}}}; + + size_t current_trajectory = 0; + + void add_waypoints(Trajectory& trajectory, + std::vector> waypoints) { + for (auto& waypoint: waypoints) { + add_waypoint(trajectory, std::get<0>(waypoint), std::get<1>(waypoint), + std::get<2>(waypoint)); + } + } + + void add_waypoint(Trajectory& trajectory, float x, float y, float v) { + Waypoint waypoint = Waypoint(); + waypoint.x = x; + waypoint.y = y; + waypoint.v = v; + trajectory.waypoints.push_back(waypoint); + } + + void timer_callback() { + if (current_trajectory < trajectories.size()) { + Trajectory trajectory_msg = Trajectory(); + trajectory_msg.header.stamp = this->now(); + add_waypoints(trajectory_msg, trajectories[current_trajectory]); + trajectory_pub_->publish(trajectory_msg); + current_trajectory++; + } + } +}; + +int main(int argc, char* argv[]) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/cev/all/cev_trajectory_follower_ros2/src/trajectory_follower.cpp b/cev/all/cev_trajectory_follower_ros2/src/trajectory_follower.cpp new file mode 100644 index 0000000..a31c78d --- /dev/null +++ b/cev/all/cev_trajectory_follower_ros2/src/trajectory_follower.cpp @@ -0,0 +1,239 @@ +#include +#include +#include "rclcpp/rclcpp.hpp" +#include "ackermann_msgs/msg/ackermann_drive.hpp" +#include "nav_msgs/msg/odometry.hpp" +#include "cev_msgs/msg/waypoint.hpp" +#include "cev_msgs/msg/trajectory.hpp" + +#include "tf2_ros/transform_listener.h" +#include "tf2_ros/buffer.h" +#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp" +#include "tf2/utils.h" + +using cev_msgs::msg::Waypoint; +using std::placeholders::_1; + +struct Coordinate { + Coordinate(float x, float y, float theta, float v): x(x), y(y), theta(theta), v(v) {} + + float x; + float y; + float theta; + float v; +}; + +class TrajectoryFollower : public rclcpp::Node { +public: + TrajectoryFollower() + : Node("trajectory_follower"), tf_buffer_(this->get_clock()), tf_listener_(tf_buffer_) { + odometry_sub_ = this->create_subscription("odometry/filtered", 1, + std::bind(&TrajectoryFollower::odometry_callback, this, _1)); + + trajectory_sub_ = this->create_subscription("trajectory", 1, + std::bind(&TrajectoryFollower::trajectory_callback, this, _1)); + + ackermann_pub_ = + this->create_publisher("rc_movement_msg", 1); + } + +private: + float min_steering_angle = -20.0 * M_PI / 180.0; + float max_steering_angle = 20.0 * M_PI / 180.0; + + float waypoint_radius = .1; + float waypoint_final_radius = .1; + + bool waypoints_initialized = false; + + float start_time; + + float timestep = 0; + + std::vector waypoints; + size_t current_waypoint = 0; + + rclcpp::Subscription::SharedPtr odometry_sub_; + rclcpp::Subscription::SharedPtr trajectory_sub_; + rclcpp::Publisher::SharedPtr ackermann_pub_; + + tf2_ros::Buffer tf_buffer_; + tf2_ros::TransformListener tf_listener_; + + float normalize_angle(float f) { + float modded = fmod(f, 2 * M_PI); + + if (modded >= M_PI) { + modded -= 2 * M_PI; + } else if (modded < -M_PI) { + modded += 2 * M_PI; + } + + return modded; + } + + float dist_to_waypoint(Coordinate& current, Waypoint& target) { + return std::sqrt(std::pow(current.x - target.x, 2) + std::pow(current.y - target.y, 2)); + } + + float angle_to_waypoint(Coordinate& current, Waypoint& target) { + float theta = current.theta; + + // RCLCPP_INFO(this->get_logger(), "Current Angle: %f", current.theta); + + float multiplier = 1.0; + + if (target.v < 0) { + theta = normalize_angle(theta + M_PI); + multiplier = -1.0; + } + + float output_angle = + multiplier + * normalize_angle(std::atan2(target.y - current.y, target.x - current.x) - theta); + + // RCLCPP_INFO(this->get_logger(), "ANGLE DIFF: %f", output_angle); + + // if (output_angle > M_PI) { + // output_angle -= 2 * M_PI; + // } else if (output_angle < -M_PI) { + // // output_angle = -(2 * M_PI - output_angle); + // output_angle += 2 * M_PI; + // } + + return output_angle; + } + + bool waypoint_reached(Coordinate& current, size_t waypoint_idx) { + Waypoint target = waypoints[waypoint_idx]; + + float dist = dist_to_waypoint(current, target); + bool final = waypoint_idx == waypoints.size() - 1; + float radius = final ? waypoint_final_radius : waypoint_radius; + + if (dist < radius) { + return true; + } + + // Check if passed line seg + float dot; + + if (final) { + Waypoint prev = waypoints[waypoint_idx - 1]; + dot = (current.x - target.x) * (target.x - prev.x) + + (current.y - target.y) * (target.y - prev.y); + } else { + Waypoint next = waypoints[waypoint_idx + 1]; + dot = (current.x - target.x) * (next.x - target.x) + + (current.y - target.y) * (next.y - target.y); + } + + return dist < .2; + } + + float find_steering_angle(Coordinate& current, Waypoint& target) { + return angle_to_waypoint(current, target); + } + + void publish_ackermann_drive(float steering_angle, float speed) { + auto ackermann_msg = ackermann_msgs::msg::AckermannDrive(); + ackermann_msg.steering_angle = normalize_angle(steering_angle); + + if (ackermann_msg.steering_angle < min_steering_angle) { + ackermann_msg.steering_angle = min_steering_angle; + } else if (ackermann_msg.steering_angle > max_steering_angle) { + ackermann_msg.steering_angle = max_steering_angle; + } + + ackermann_msg.speed = speed; + + ackermann_pub_->publish(ackermann_msg); + } + + void odometry_callback(const nav_msgs::msg::Odometry::SharedPtr msg) { + if (!waypoints_initialized || current_waypoint >= waypoints.size()) { + publish_ackermann_drive(0.0, 0.0); + return; + } + + float current_time = static_cast(this->now().seconds()); + float time_elapsed = current_time - start_time; + + if (time_elapsed + > timestep * waypoints.size() / 2.0) { // Too much time elapsed without a new message + publish_ackermann_drive(0.0, 0.0); + return; + } + + geometry_msgs::msg::TransformStamped transform; + + geometry_msgs::msg::PoseStamped base_link_pose; + base_link_pose.header = msg->header; + base_link_pose.pose = msg->pose.pose; + + geometry_msgs::msg::PoseStamped map_pose; + + try { + transform = tf_buffer_.lookupTransform("map", "odom", tf2::TimePointZero); + tf2::doTransform(base_link_pose, map_pose, transform); + } catch (const tf2::TransformException& ex) { + RCLCPP_WARN(this->get_logger(), "Could not transform odom to map: %s", ex.what()); + return; + } + + float qw = map_pose.pose.orientation.w; + float qx = map_pose.pose.orientation.x; + float qy = map_pose.pose.orientation.y; + float qz = map_pose.pose.orientation.z; + + float yaw = normalize_angle(atan2(2.0 * (qw * qz + qx * qy), + 1.0 - 2.0 * (qy * qy + qz * qz))); + + Coordinate current = Coordinate(map_pose.pose.position.x, map_pose.pose.position.y, yaw, + msg->twist.twist.linear.x); + + // Skip reached waypoints + while (waypoint_reached(current, current_waypoint)) { + current_waypoint++; + if (current_waypoint >= waypoints.size()) { + waypoints_initialized = false; + publish_ackermann_drive(0.0, 0.0); + return; + } + } + + Waypoint target = waypoints[current_waypoint]; + + // RCLCPP_INFO(this->get_logger(), "Target: %f, %f", target.x, target.y); + + float steering_angle = find_steering_angle(current, target); + + // RCLCPP_INFO(this->get_logger(), "Target Steering: %f", steering_angle); + + float vel = 0; + + if (target.v > .01 && target.v < .4) { + target.v = .4; + } else if (target.v < -.01 && target.v > -.4) { + target.v = -.4; + } + + // publish_ackermann_drive(steering_angle, vel); + publish_ackermann_drive(target.tau, target.v); + } + + void trajectory_callback(const cev_msgs::msg::Trajectory::SharedPtr msg) { + waypoints = msg->waypoints; + current_waypoint = 0; + timestep = msg->timestep; + waypoints_initialized = true; + start_time = static_cast(this->now().seconds()); + } +}; + +int main(int argc, char* argv[]) { + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/cev/all/cev_vision_ros2/.gitignore b/cev/all/cev_vision_ros2/.gitignore new file mode 100644 index 0000000..8cf491d --- /dev/null +++ b/cev/all/cev_vision_ros2/.gitignore @@ -0,0 +1 @@ +bag/ diff --git a/cev/all/cev_vision_ros2/config/occupancy.yaml b/cev/all/cev_vision_ros2/config/occupancy.yaml new file mode 100644 index 0000000..75fe76c --- /dev/null +++ b/cev/all/cev_vision_ros2/config/occupancy.yaml @@ -0,0 +1,11 @@ +occupancy_transformer: + ros__parameters: + map_x_size: 10.0 + map_y_size: 20.0 + map_resolution: 0.1 + fuzz_threshold: 0.02 + base_link_height: 0.1 + ride_height: 0.05 + car_height: 0.15 + base_link_frame: base_link + point_cloud_topic: /zed/zed_node/point_cloud/cloud_registered diff --git a/cev/all/cev_vision_ros2/launch/bag.py b/cev/all/cev_vision_ros2/launch/bag.py new file mode 100644 index 0000000..436f87b --- /dev/null +++ b/cev/all/cev_vision_ros2/launch/bag.py @@ -0,0 +1,35 @@ +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 +from launch_ros.actions import Node + + +def get_path(package, dir, file): + return os.path.join(get_package_share_directory(package), dir, file) + + +def launch(package, file, launch_folder="launch", arguments={}): + return IncludeLaunchDescription( + PythonLaunchDescriptionSource(get_path(package, launch_folder, file)), + launch_arguments=arguments.items(), + ) + + +def generate_launch_description(): + config = os.path.join( + get_package_share_directory("vision"), "config", "occupancy.yaml" + ) + + return LaunchDescription( + [ + Node( + package="vision", + executable="occupancy_transformer", + name="occupancy_transformer", + parameters=[config, {"use_sim_time": True}], + ), + ] + ) diff --git a/cev/all/cev_vision_ros2/launch/launch.py b/cev/all/cev_vision_ros2/launch/launch.py new file mode 100644 index 0000000..0e3250b --- /dev/null +++ b/cev/all/cev_vision_ros2/launch/launch.py @@ -0,0 +1,59 @@ +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 +from launch_ros.actions import Node + + +def get_path(package, dir, file): + return os.path.join(get_package_share_directory(package), dir, file) + + +def launch(package, file, launch_folder="launch", arguments={}): + return IncludeLaunchDescription( + PythonLaunchDescriptionSource(get_path(package, launch_folder, file)), + launch_arguments=arguments.items(), + ) + + +def generate_launch_description(): + config = os.path.join( + get_package_share_directory("vision"), "config", "occupancy.yaml" + ) + + return LaunchDescription( + [ + launch( + "zed_wrapper", + "zed_camera.launch.py", + arguments={ + "camera_model": "zed2", + "publish_tf": "false", + "publish_map_tf": "false", + }, + ), + Node( + package="tf2_ros", + executable="static_transform_publisher", + name="zed_camera_base_link", + arguments=[ + "0", + "0", + "0", + "0", + "0", + "0", + "base_link", + "zed_camera_link", + ], + ), + Node( + package="vision", + executable="occupancy_transformer", + name="occupancy_transformer", + parameters=[config], + ), + ] + ) diff --git a/cev/all/cev_vision_ros2/package.xml b/cev/all/cev_vision_ros2/package.xml new file mode 100644 index 0000000..d74ddc2 --- /dev/null +++ b/cev/all/cev_vision_ros2/package.xml @@ -0,0 +1,16 @@ + + + + vision + 0.0.0 + TODO: Package description + utku + TODO: License declaration + + zed_wrapper + tf2_ros + + + ament_python + + diff --git a/cev/all/cev_vision_ros2/resource/vision b/cev/all/cev_vision_ros2/resource/vision new file mode 100644 index 0000000..e69de29 diff --git a/cev/all/cev_vision_ros2/rviz/viz.rviz b/cev/all/cev_vision_ros2/rviz/viz.rviz new file mode 100644 index 0000000..2552509 --- /dev/null +++ b/cev/all/cev_vision_ros2/rviz/viz.rviz @@ -0,0 +1,244 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 109 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + Splitter Ratio: 0.5 + Tree Height: 1107 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Goal Pose1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 + - Class: rviz_common/Time + Experimental: false + Name: Time + SyncMode: 0 + SyncSource: PointCloud2 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: true + base_link: + Value: true + zed_baro_link: + Value: true + zed_camera_center: + Value: true + zed_camera_link: + Value: true + zed_imu_link: + Value: true + zed_left_camera_frame: + Value: true + zed_left_camera_optical_frame: + Value: true + zed_mag_link: + Value: true + zed_right_camera_frame: + Value: true + zed_right_camera_optical_frame: + Value: true + zed_temp_left_link: + Value: true + zed_temp_right_link: + Value: true + Marker Scale: 1 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: false + Tree: + base_link: + zed_camera_link: + zed_camera_center: + zed_baro_link: + {} + zed_left_camera_frame: + zed_imu_link: + {} + zed_left_camera_optical_frame: + {} + zed_temp_left_link: + {} + zed_mag_link: + {} + zed_right_camera_frame: + zed_right_camera_optical_frame: + {} + zed_temp_right_link: + {} + Update Interval: 0 + Value: true + - Alpha: 0.699999988079071 + Class: rviz_default_plugins/Map + Color Scheme: map + Draw Behind: false + Enabled: true + Name: Map + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /occupancy + Update Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /occupancy_updates + Use Timestamp: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: RGB8 + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: + Depth: 5 + Durability Policy: Volatile + Filter size: 10 + History Policy: Keep Last + Reliability Policy: Reliable + Value: /zed/zed_node/point_cloud/cloud_registered + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: base_link + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 8.407171249389648 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 1.9439679384231567 + Y: -0.24377664923667908 + Z: 0.705398440361023 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.5103979706764221 + Target Frame: + Value: Orbit (rviz) + Yaw: 3.115450143814087 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1493 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd00000004000000000000018700000510fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000008800fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c0061007900730100000055000005100000012a00fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000011800000510fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000005500000510000000e800fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000009f000000044fc0100000002fb0000000800540069006d00650100000000000009f00000038300fffffffb0000000800540069006d006501000000000000045000000000000000000000073f0000051000000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Time: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 2544 + X: 8 + Y: 60 diff --git a/cev/all/cev_vision_ros2/setup.cfg b/cev/all/cev_vision_ros2/setup.cfg new file mode 100644 index 0000000..b190bc5 --- /dev/null +++ b/cev/all/cev_vision_ros2/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/vision +[install] +install_scripts=$base/lib/vision diff --git a/cev/all/cev_vision_ros2/setup.py b/cev/all/cev_vision_ros2/setup.py new file mode 100644 index 0000000..2bab3de --- /dev/null +++ b/cev/all/cev_vision_ros2/setup.py @@ -0,0 +1,27 @@ +from setuptools import find_packages, setup + +package_name = "vision" + +setup( + name=package_name, + version="0.0.0", + packages=find_packages(exclude=["test"]), + data_files=[ + ("share/ament_index/resource_index/packages", ["resource/" + package_name]), + ("share/" + package_name, ["package.xml"]), + (f"share/{package_name}/config", ["config/occupancy.yaml"]), + (f"share/{package_name}/launch", ["launch/launch.py", "launch/bag.py"]), + ], + install_requires=["setuptools"], + zip_safe=True, + maintainer="Utku Melemetci", + maintainer_email="55263178+Yey007@users.noreply.github.com", + description="Vision code for mini-cars.", + license="TODO", + tests_require=["pytest"], + entry_points={ + "console_scripts": [ + "occupancy_transformer = vision.occupancy_transformer:main" + ], + }, +) diff --git a/cev/all/cev_vision_ros2/vision/__init__.py b/cev/all/cev_vision_ros2/vision/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cev/all/cev_vision_ros2/vision/occupancy_transformer.py b/cev/all/cev_vision_ros2/vision/occupancy_transformer.py new file mode 100644 index 0000000..7174425 --- /dev/null +++ b/cev/all/cev_vision_ros2/vision/occupancy_transformer.py @@ -0,0 +1,249 @@ +import struct + +import numpy as np +import numpy.typing as npt +import rclpy +import rclpy.node +import rclpy.parameter +import rclpy.qos +import rclpy.time +import tf2_ros +from geometry_msgs.msg import Point, Pose, Quaternion +from nav_msgs.msg import OccupancyGrid +from rcl_interfaces.msg import ParameterDescriptor +from sensor_msgs.msg import PointCloud2, PointField + + +class OccupancyTransformerNode(rclpy.node.Node): + def __init__(self): + super().__init__("occupancy_transformer") + + self.declare_parameter( + "point_cloud_topic", + "point_cloud", + ParameterDescriptor( + description="The topic to subscribe to for point cloud data" + ), + ) + self.declare_parameter( + "map_x_size", + 10.0, + ParameterDescriptor( + description="The size of the map in meters along the x-axis of base_link_frame" + ), + ) + self.declare_parameter( + "map_y_size", + 10.0, + ParameterDescriptor( + description="The size of the map in meters along the y-axis of base_link_frame" + ), + ) + self.declare_parameter( + "map_resolution", + 0.1, + ParameterDescriptor( + description="The resolution of the map in meters per cell" + ), + ) + self.declare_parameter( + "fuzz_threshold", + 0.1, + ParameterDescriptor( + description="The threshold (in meters) beyond car dimensions to consider a point as an obstacle." + ), + ) + self.declare_parameter( + "base_link_height", + rclpy.Parameter.Type.DOUBLE, + ParameterDescriptor( + description="The height (in meters) of base_link_frame above the ground." + ), + ) + self.declare_parameter( + "ride_height", + rclpy.Parameter.Type.DOUBLE, + ParameterDescriptor( + description="The height (in meters) above the ground plane of the tallest traversable obstacle." + ), + ) + self.declare_parameter( + "car_height", + rclpy.Parameter.Type.DOUBLE, + ParameterDescriptor( + description="The height (in meters) of the tallest point on the car." + ), + ) + self.declare_parameter( + "base_link_frame", + "base_link", + ParameterDescriptor(description="The frame_id of the base link frame."), + ) + + self.subscription = self.create_subscription( + PointCloud2, + self.get_parameter("point_cloud_topic").value, + self.pc_callback, + rclpy.qos.qos_profile_sensor_data, + ) + self.publisher = self.create_publisher(OccupancyGrid, "occupancy", 10) + + self.tf_buffer = tf2_ros.Buffer() + self.tf_listener = tf2_ros.TransformListener(self.tf_buffer, self) + + def pc_callback(self, cloud: PointCloud2): + map_x_m = self.get_parameter("map_x_size").value + map_y_m = self.get_parameter("map_y_size").value + map_res_m = self.get_parameter("map_resolution").value + + map_x_cells = int(map_x_m / map_res_m) + map_y_cells = int(map_y_m / map_res_m) + + base_link_height_m = self.get_parameter("base_link_height").value + ride_height_m = self.get_parameter("ride_height").value + car_height_m = self.get_parameter("car_height").value + fuzz_threshold_m = self.get_parameter("fuzz_threshold").value + + base_link_frame = self.get_parameter("base_link_frame").value + transform = self.tf_buffer.lookup_transform( + base_link_frame, cloud.header.frame_id, rclpy.time.Time() + ) + translation = transform.transform.translation + translation = np.array([translation.x, translation.y, translation.z]) + rotation = transform.transform.rotation + rotation = quaternion_to_rotation_matrix( + (rotation.w, rotation.x, rotation.y, rotation.z) + ) + + point_cloud = parse_points(cloud) + point_cloud = point_cloud[~np.isnan(point_cloud).any(axis=1)] + base_link_pc = point_cloud @ rotation.T + translation + + pc_z = base_link_pc[:, 2] + filtered_pc = base_link_pc[ + (pc_z < (car_height_m - base_link_height_m + fuzz_threshold_m)) + & (pc_z > (-(base_link_height_m - ride_height_m) - fuzz_threshold_m)) + ] + + grid = np.zeros((map_x_cells, map_y_cells), dtype=np.int8) + + cell_x = filtered_pc[:, 0] / map_res_m + cell_y = (filtered_pc[:, 1] + map_y_m / 2) / map_res_m + cells = np.stack((cell_x, cell_y), axis=-1).astype(np.int32) + + in_range = cells[ + ( + (cells[:, 0] >= 0) + & (cells[:, 0] < map_x_cells) + & (cells[:, 1] >= 0) + & (cells[:, 1] < map_y_cells) + ) + ] + in_range = cells + + grid[in_range[:, 0], in_range[:, 1]] = 255 + + # nav_msgs/OccupancyGrid: + # The map data, in row-major order, starting with (0,0). + # Cell (1, 0) will be listed second, representing the next cell in the x direction. + # Cell (0, 1) will be at the index equal to info.width, followed by (1, 1). + # The values inside are application dependent, but frequently, + # 0 represents unoccupied, 1 represents definitely occupied, and + # -1 represents unknown. + + # flatten in column-major order, as we've made our rows constant x but that is not what nav_msgs/OccupancyGrid wants + flat = grid.flatten(order="F") + + occupancy_grid = OccupancyGrid() + + occupancy_grid.header.stamp = cloud.header.stamp + occupancy_grid.header.frame_id = base_link_frame + + occupancy_grid.info.map_load_time = cloud.header.stamp + occupancy_grid.info.resolution = map_res_m + occupancy_grid.info.width = map_x_cells + occupancy_grid.info.height = map_y_cells + + origin_offset = (-map_y_cells * map_res_m) / 2 + map_origin = Pose( + position=Point(x=0.0, y=origin_offset, z=-base_link_height_m), + orientation=Quaternion(x=0.0, y=0.0, z=0.0, w=1.0), + ) + occupancy_grid.info.origin = map_origin + + occupancy_grid.data = list(map(int, flat)) + + self.publisher.publish(occupancy_grid) + + +def parse_points(cloud: PointCloud2) -> npt.NDArray[np.float32]: + # TODO: avoid repetition (if we care) + (x_field,) = [field for field in cloud.fields if field.name == "x"] + (y_field,) = [field for field in cloud.fields if field.name == "y"] + (z_field,) = [field for field in cloud.fields if field.name == "z"] + x_format = get_unpack_format(x_field, cloud.is_bigendian) + y_format = get_unpack_format(y_field, cloud.is_bigendian) + z_format = get_unpack_format(z_field, cloud.is_bigendian) + + n_points = len(cloud.data) // cloud.point_step + result = np.zeros((n_points, 3), dtype=np.float32) + + # TODO: this probably needs to get faster using iter_unpack and pad bytes + for i in range(n_points): + point_offset = i * cloud.point_step + (result[i][0],) = struct.unpack_from( + x_format, buffer=cloud.data, offset=point_offset + x_field.offset + ) + (result[i][1],) = struct.unpack_from( + y_format, buffer=cloud.data, offset=point_offset + y_field.offset + ) + (result[i][2],) = struct.unpack_from( + z_format, buffer=cloud.data, offset=point_offset + z_field.offset + ) + + return result + + +def get_unpack_format(field: PointField, big_endian: bool): + endian_format = ">" if big_endian else "<" + count_format = str(field.count) + + type_format_dict = { + PointField.INT8: "b", + PointField.UINT8: "B", + PointField.INT16: "h", + PointField.UINT16: "H", + PointField.INT32: "i", + PointField.UINT32: "I", + PointField.FLOAT32: "f", + PointField.FLOAT64: "d", + } + type_format = type_format_dict.get(field.datatype, "") + + return f"{endian_format}{count_format}{type_format}" + + +def quaternion_to_rotation_matrix(q): + w, x, y, z = q + r = np.array( + [ + [1 - 2 * y**2 - 2 * z**2, 2 * x * y - 2 * z * w, 2 * x * z + 2 * y * w], + [2 * x * y + 2 * z * w, 1 - 2 * x**2 - 2 * z**2, 2 * y * z - 2 * x * w], + [2 * x * z - 2 * y * w, 2 * y * z + 2 * x * w, 1 - 2 * x**2 - 2 * y**2], + ] + ) + return r + + +def main(): + rclpy.init() + + occupancy_transformer = OccupancyTransformerNode() + rclpy.spin(occupancy_transformer) + + occupancy_transformer.destroy_node() + rclpy.shutdown() + + +if __name__ == "__main__": + main() diff --git a/cev/cev_autobrake_ros2 b/cev/cev_autobrake_ros2 deleted file mode 160000 index b49dbf0..0000000 --- a/cev/cev_autobrake_ros2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b49dbf0e67936d533bc2da42b64db27b08c67e22 diff --git a/cev/cev_encoder_odometry_ros2 b/cev/cev_encoder_odometry_ros2 deleted file mode 160000 index 69844b6..0000000 --- a/cev/cev_encoder_odometry_ros2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 69844b675183fc453fde768896f5d531d62e900e diff --git a/cev/cev_localization_ros2 b/cev/cev_localization_ros2 deleted file mode 160000 index 8ed79c6..0000000 --- a/cev/cev_localization_ros2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8ed79c6a5c06c6d4bd3168d147f34fd83735ea2e diff --git a/cev/cev_planner_ros2 b/cev/cev_planner_ros2 deleted file mode 160000 index 098009a..0000000 --- a/cev/cev_planner_ros2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 098009a81c3fcbf9815d64897482e08933ad1c34 diff --git a/cev/cev_teleop_ros2 b/cev/cev_teleop_ros2 deleted file mode 160000 index e2e2e9a..0000000 --- a/cev/cev_teleop_ros2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e2e2e9ae0e9151eeb86205b350a5ccaa26a894b7 diff --git a/cev/cev_trajectory_follower_ros2 b/cev/cev_trajectory_follower_ros2 deleted file mode 160000 index 5d07b17..0000000 --- a/cev/cev_trajectory_follower_ros2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5d07b170f237278856859d90b01c339f4f389a11 diff --git a/cev/cev_vision_ros2 b/cev/cev_vision_ros2 deleted file mode 160000 index 0e0cac9..0000000 --- a/cev/cev_vision_ros2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e0cac9705df247c7e50d376872ecb28e2844fcb diff --git a/mux/CMakeLists.txt b/cev/mini/mux/CMakeLists.txt similarity index 100% rename from mux/CMakeLists.txt rename to cev/mini/mux/CMakeLists.txt diff --git a/cev/mini/mux/config/.gitkeep b/cev/mini/mux/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mux/launch/launch.py b/cev/mini/mux/launch/launch.py similarity index 100% rename from mux/launch/launch.py rename to cev/mini/mux/launch/launch.py diff --git a/mux/package.xml b/cev/mini/mux/package.xml similarity index 100% rename from mux/package.xml rename to cev/mini/mux/package.xml diff --git a/mux/src/mux.cpp b/cev/mini/mux/src/mux.cpp similarity index 100% rename from mux/src/mux.cpp rename to cev/mini/mux/src/mux.cpp diff --git a/serial_com/CMakeLists.txt b/cev/mini/serial_com/CMakeLists.txt similarity index 100% rename from serial_com/CMakeLists.txt rename to cev/mini/serial_com/CMakeLists.txt diff --git a/cev/mini/serial_com/config/.gitkeep b/cev/mini/serial_com/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/serial_com/launch/launch.py b/cev/mini/serial_com/launch/launch.py similarity index 100% rename from serial_com/launch/launch.py rename to cev/mini/serial_com/launch/launch.py diff --git a/serial_com/package.xml b/cev/mini/serial_com/package.xml similarity index 100% rename from serial_com/package.xml rename to cev/mini/serial_com/package.xml diff --git a/serial_com/src/arduino_com.cpp b/cev/mini/serial_com/src/arduino_com.cpp similarity index 100% rename from serial_com/src/arduino_com.cpp rename to cev/mini/serial_com/src/arduino_com.cpp diff --git a/cev_msgs b/cev_msgs deleted file mode 160000 index 4ec3201..0000000 --- a/cev_msgs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4ec32014c3ef0fc6a594d4e50fd967c3d5afaf46 diff --git a/hardware/Onboard/Onboard.ino b/hardware/mini/Onboard/Onboard.ino similarity index 100% rename from hardware/Onboard/Onboard.ino rename to hardware/mini/Onboard/Onboard.ino diff --git a/hardware/QuickTest/QuickTest.ino b/hardware/mini/QuickTest/QuickTest.ino similarity index 100% rename from hardware/QuickTest/QuickTest.ino rename to hardware/mini/QuickTest/QuickTest.ino diff --git a/install.sh b/install.sh deleted file mode 100755 index 757e3eb..0000000 --- a/install.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -apt install -y \ - libeigen3-dev \ - libnlopt-dev \ - libnlopt-cxx-dev \ - libopencv-dev \ - libyaml-cpp-dev \ - libqt5serialport5-dev \ - libsuitesparse-dev \ - libceres-dev \ - ros-humble-slam-toolbox diff --git a/entrypoint.sh b/scripts/deployed_entrypoint.sh similarity index 100% rename from entrypoint.sh rename to scripts/deployed_entrypoint.sh diff --git a/scripts/install_extra.sh b/scripts/install_extra.sh new file mode 100755 index 0000000..6a58aec --- /dev/null +++ b/scripts/install_extra.sh @@ -0,0 +1,11 @@ +#!/bin/bash +apt-get install -y \ + libeigen3-dev \ + libnlopt-dev \ + libnlopt-cxx-dev \ + libopencv-dev \ + libyaml-cpp-dev \ + libqt5serialport5-dev \ + libsuitesparse-dev \ + libceres-dev \ + ros-humble-slam-toolbox