diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5668063 --- /dev/null +++ b/.clang-format @@ -0,0 +1,27 @@ +--- +Language: Cpp +BasedOnStyle: Google + +TabWidth: 2 +IndentWidth: 4 +AccessModifierOffset: -4 +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: "false" + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBraces: Custom +ColumnLimit: 120 +DerivePointerAlignment: false +PointerAlignment: Left +ReflowComments: false +... diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..286d453 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,18 @@ +name: CI + +on: [ push, pull_request ] + +jobs: + industrial_ci: + runs-on: ubuntu-latest + strategy: + matrix: + env: + - { ROS_DISTRO: humble, ROS_REPO: testing } + - { ROS_DISTRO: humble, ROS_REPO: main } + name: ROS ${{ matrix.ROS_DISTRO }} (${{ matrix.ROS_REPO }}) + steps: + - uses: actions/checkout@v3 + - uses: 'ros-industrial/industrial_ci@master' + env: ${{ matrix.env }} + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d44b693 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +log +build +install +.vscode +.cache +compile_commands.json \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..39e95cc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 3.8) +project(TODO_PACKAGE_NAME) +include(FetchContent) + +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) +# Messages TODO_EXTRA +find_package(ackermann_msgs REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(std_msgs REQUIRED) +# OpenCV TODO_EXTRA +find_package(cv_bridge REQUIRED) +find_package(OpenCV 4.2.0 REQUIRED) + +# Add source for node executable (link non-ros dependencies here) +add_executable(TODO_PACKAGE_NAME src/TODO_NODE_NAME.cpp src/TODO_NODE_NAME_node.cpp) +target_include_directories(TODO_PACKAGE_NAME PUBLIC + $ + $) +target_compile_features(TODO_PACKAGE_NAME PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17 + +# Make ros deps a variable so they get linked to tests as well +set(dependencies + rclcpp + # Messages TODO_EXTRA + ackermann_msgs + sensor_msgs + std_msgs + # OpenCv TODO_EXTRA + cv_bridge + OpenCV + ) + +# Link ros dependencies +ament_target_dependencies( + TODO_PACKAGE_NAME + ${dependencies} +) + +install(TARGETS TODO_PACKAGE_NAME + DESTINATION lib/${PROJECT_NAME}) + +# Uncomment below to make launch files available if created +#install( +# DIRECTORY launch config +# DESTINATION share/${PROJECT_NAME}/ +#) + +if (BUILD_TESTING) + # Manually invoke clang format so it actually uses our file + find_package(ament_cmake_clang_format REQUIRED) + ament_clang_format(CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/.clang-format) + + find_package(ament_cmake_gtest REQUIRED) + + # Add unit tests + ament_add_gtest(${PROJECT_NAME}-test + tests/unit.cpp + # Remember to add node source files + src/TODO_NODE_NAME_node.cpp + ) + ament_target_dependencies(${PROJECT_NAME}-test ${dependencies}) + target_include_directories(${PROJECT_NAME}-test PUBLIC + $ + $) +endif () + +ament_package() diff --git a/README.md b/README.md new file mode 100644 index 0000000..3369c0a --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +An opinionated ROS2 C++ node template, optimised for ISC. + +# Instructions + +1. Clone repo inside your workspaces src directory (Ex. phnx_ws/src) +2. `rosdep install --from-paths . --ignore-src -r -y` to install deps +3. `colcon build` to make sure the repo builds before you mess with it +4. Replace the following in both file names and code exactly and consistently. + 1. TODO_PACKAGE_NAME: Replace with the package name. Use snake case. Ex. `data_logger` + 2. TODO_NODE_NAME: Replace with the node name. Use Pascal case. Ex. `DataLogger` +5. `colcon build` again. If it builds, you are done +6. Rename outer folder +7. Review the optional dependencies, and remove what you do not need + +# Dependencies +Some common extra dependencies are included. Review them and remove what you don't need. +These are marked with TODO_EXTRA. + +# Features + +- Unit tests +- ROS-Industrial github CI (will test units and lints) +- C++ formatting via clangformat +- A selection of sane lints +- A single node setup in a multithreaded executor + +# File structure + +``` +. +├── include +│   └── TODO_PACKAGE_NAME +│   └── TODO_NODE_NAME_node.hpp +├── package.xml +├── README.md +├── src +│   ├── TODO_NODE_NAME.cpp +│   └── TODO_NODE_NAME_node.cpp +└── tests + └── unit.cpp +``` + +TODO_NODE_NAME_node: Source files for the ROS2 node object itself, and only itself + +TODO_NODE_NAME.cpp: Source for the main function of the node, and only the main function + +tests/unit.cpp: Example file for unit tests. This is linked to the node and ros, so both can be used \ No newline at end of file diff --git a/include/TODO_PACKAGE_NAME/TODO_NODE_NAME_node.hpp b/include/TODO_PACKAGE_NAME/TODO_NODE_NAME_node.hpp new file mode 100644 index 0000000..d8fe4d5 --- /dev/null +++ b/include/TODO_PACKAGE_NAME/TODO_NODE_NAME_node.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +class TODO_NODE_NAME : public rclcpp::Node { +private: + rclcpp::Publisher::SharedPtr pub; + + rclcpp::Subscription::SharedPtr sub; + +public: + TODO_NODE_NAME(const rclcpp::NodeOptions& options); + + /// subscriber callback + void sub_cb(std_msgs::msg::String::SharedPtr msg); +}; diff --git a/package.xml b/package.xml new file mode 100644 index 0000000..62544a7 --- /dev/null +++ b/package.xml @@ -0,0 +1,36 @@ + + + + TODO_PACKAGE_NAME + 0.1.0 + A node template + Andrew Ealovega + MIT + + ament_cmake + + rclcpp + + + ackermann_msgs + sensor_msgs + std_msgs + + + cv_bridge + libopencv-dev + + ament_lint_auto + ament_cmake_flake8 + ament_cmake_xmllint + ament_cmake_clang_format + ament_cmake_cppcheck + ament_cmake_pep257 + ament_clang_tidy + ament_cmake_gtest + ament_cmake_nose + + + ament_cmake + + diff --git a/src/TODO_NODE_NAME.cpp b/src/TODO_NODE_NAME.cpp new file mode 100644 index 0000000..9ec0141 --- /dev/null +++ b/src/TODO_NODE_NAME.cpp @@ -0,0 +1,17 @@ +#include "TODO_PACKAGE_NAME/TODO_NODE_NAME_node.hpp" + +int main(int argc, char** argv) { + // Setup runtime + rclcpp::init(argc, argv); + rclcpp::executors::MultiThreadedExecutor exec; + rclcpp::NodeOptions options; + + // Add nodes to executor + auto node = std::make_shared(options); + exec.add_node(node); + + // Run + exec.spin(); + rclcpp::shutdown(); + return 0; +} diff --git a/src/TODO_NODE_NAME_node.cpp b/src/TODO_NODE_NAME_node.cpp new file mode 100644 index 0000000..3f7bc6e --- /dev/null +++ b/src/TODO_NODE_NAME_node.cpp @@ -0,0 +1,27 @@ +#include "TODO_PACKAGE_NAME/TODO_NODE_NAME_node.hpp" + +// For _1 +using namespace std::placeholders; + +TODO_NODE_NAME::TODO_NODE_NAME(const rclcpp::NodeOptions& options) : Node("TODO_NODE_NAME", options) { + // Parameters + float x = this->declare_parameter("foo", -10.0); + + // Pub Sub + this->sub = + this->create_subscription("/str", 1, std::bind(&TODO_NODE_NAME::sub_cb, this, _1)); + this->pub = this->create_publisher("/run_folder", 1); + + // Log a sample log + RCLCPP_INFO(this->get_logger(), "You passed %f", x); + + // Send a sample message + std_msgs::msg::String msg{}; + msg.data = std::string{"Hello World!"}; + pub->publish(msg); +} + +void TODO_NODE_NAME::sub_cb(const std_msgs::msg::String::SharedPtr msg) { + // Echo message + this->pub->publish(*msg); +} diff --git a/tests/unit.cpp b/tests/unit.cpp new file mode 100644 index 0000000..e28e06d --- /dev/null +++ b/tests/unit.cpp @@ -0,0 +1,17 @@ +#include + +#include + +#include "TODO_PACKAGE_NAME/TODO_NODE_NAME_node.hpp" + +TEST(TODO_NODE_NAME, Test1) {} + +int main(int argc, char** argv) { + rclcpp::init(0, nullptr); + + ::testing::InitGoogleTest(&argc, argv); + auto res = RUN_ALL_TESTS(); + + rclcpp::shutdown(); + return res; +} \ No newline at end of file