diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fe726c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode/ +install/ +build/ +log/ \ No newline at end of file diff --git a/README.md b/README.md index 77bcc77..32a1a60 100644 --- a/README.md +++ b/README.md @@ -1 +1,12 @@ -# software_challenge \ No newline at end of file +# UWRT Software Training Assignment - Complete + +## How To Run +1. Clone this repository. +2. Source your ROS2 installation. `source /opt/ros/foxy/setup.bash`. +3. `cd` into this repository. +4. Run `colcon build` in this repository. +5. Open a new terminal. +6. `cd` into this repository in the new terminal. +7. Run `. install/setup.bash` in the new terminal. +8. Run `ros2 launch software_training_assignment testlaunch.py` in the new terminal. +9. Sit back and watch the demo for 10 seconds. diff --git a/software_training_assignment/CMakeLists.txt b/software_training_assignment/CMakeLists.txt new file mode 100644 index 0000000..e04374a --- /dev/null +++ b/software_training_assignment/CMakeLists.txt @@ -0,0 +1,188 @@ +cmake_minimum_required(VERSION 3.5) +project(software_training_assignment) + +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +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(turtlesim REQUIRED) +find_package(rclcpp_components REQUIRED) +find_package(std_msgs REQUIRED) +find_package(rcutils REQUIRED) +find_package(rcl REQUIRED) +find_package(rclcpp_action REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(builtin_interfaces REQUIRED) +find_package(rosidl_default_generators REQUIRED) + + +#include the 'include' directory +include_directories(include) + +# custom services and messages and actions +rosidl_generate_interfaces(${PROJECT_NAME} + "srv/ResetMovingTurtle.srv" + "msg/Software.msg" + "action/Software.action" + DEPENDENCIES std_msgs geometry_msgs builtin_interfaces + ) +ament_export_dependencies(rosidl_default_runtime) + +#create resource which references the libraries in the binary bin +set(node_plugins "") + + +#add clear_turtles as plugin +add_library(clear_turtles SHARED + src/clear_turtles.cpp) +target_compile_definitions(clear_turtles + PRIVATE "SOFTWARE_TRAINING_DLL") +ament_target_dependencies(clear_turtles + "rclcpp" + "rclcpp_components" + "turtlesim" + "std_msgs" + "geometry_msgs") +# rosidl_target_interfaces(clear_turtles ${PROJECT_NAME} "rosidl_typesupport_cpp") # need this for custom messages - idk why tho +rclcpp_components_register_node(clear_turtles PLUGIN "composition::clear_turtles" EXECUTABLE turtle_clear) +# set(node_plugins PLUGIN "${node_plugins}composition::clear_turtles;$" EXECUTABLE turtle_clear) + + + +#add spawn_turtle_nodelets as plugin +add_library(turtle_spawn SHARED + src/spawn_turtle_nodelet.cpp) +target_compile_definitions(turtle_spawn + PRIVATE "SOFTWARE_TRAINING_DLL") +ament_target_dependencies(turtle_spawn + "rclcpp" + "rclcpp_components" + "turtlesim" + "std_msgs" + "geometry_msgs") +rclcpp_components_register_node(turtle_spawn PLUGIN "composition::spawn_turtle_nodelet" EXECUTABLE turtle_spawner) +# set(node_plugins PLUGIN "${node_plugins}composition::spawn_turtle_nodelet;$" EXECUTABLE turtle_spawner) + + +#add turtle publisher nodelet +add_library(turtle_pub SHARED + src/turtle_publisher.cpp) +target_compile_definitions(turtle_pub + PRIVATE "SOFTWARE_TRAINING_DLL" + ) +ament_target_dependencies(turtle_pub + "rclcpp" + "rclcpp_components" + "turtlesim" + "std_msgs") +rosidl_target_interfaces(turtle_pub ${PROJECT_NAME} "rosidl_typesupport_cpp") # need this for custom messages - idk why tho +rclcpp_components_register_node(turtle_pub PLUGIN "composition::turtle_publisher" EXECUTABLE turtle_main_pub) +# set(node_plugins PLUGIN "${node_plugins}composition::turtle_publisher;$" EXECUTABLE turtle_main_pub) + +#add moving turtle service +add_library(turtle_service SHARED + src/reset_moving_turtle.cpp) +target_compile_definitions(turtle_service + PRIVATE "SOFTWARE_TRAINING_DLL" + ) +ament_target_dependencies(turtle_service + "rclcpp" + "rclcpp_components" + "turtlesim" + "turtlesim" + "std_msgs") +rosidl_target_interfaces(turtle_service ${PROJECT_NAME} "rosidl_typesupport_cpp") # need for custom srv +rclcpp_components_register_node(turtle_service PLUGIN "composition::reset_moving_turtle" EXECUTABLE moving_turtle_reset) +# set(node_plugins PLUGIN "${node_plugins}composition::reset_moving_turtle;$") + + +add_library(turtle_action_server SHARED + src/action_turtle.cpp) +target_compile_definitions(turtle_action_server + PRIVATE "SOFTWARE_TRAINING_DLL") +ament_target_dependencies(turtle_action_server + "rclcpp" + "rclcpp_components" + "turtlesim" + "rclcpp_action" + "std_msgs" + "geometry_msgs") +rosidl_target_interfaces(turtle_action_server ${PROJECT_NAME} "rosidl_typesupport_cpp") # needed for custom action +# with this we can execute the component as a node -cd ros2 run software_training turtle_action +rclcpp_components_register_node(turtle_action_server PLUGIN "composition::action_turtle" EXECUTABLE turtle_action) +# set(node_plugins "${node_plugins}composition::action_turtle;$") + + +# add_library(turtle_action_client SHARED +# src/action_client.cpp) +# target_compile_definitions(turtle_action_client +# PRIVATE "SOFTWARE_TRAINING_DLL") +# ament_target_dependencies(turtle_action_client +# "rclcpp" +# "rclcpp_components" +# "turtlesim" +# "rclcpp_action" +# "std_msgs" +# "geometry_msgs") +# rosidl_target_interfaces(turtle_action_client ${PROJECT_NAME} "rosidl_typesupport_cpp") # needed for custom action +# # with this we can execute the component as a node - ros2 run software_training turtle_action +# rclcpp_components_register_node(turtle_action_client PLUGIN "composition::action_turtle" EXECUTABLE turtle_client) +# set(node_plugins "${node_plugins}composition::action_client;$") + + +#tell where to put binaries +install(TARGETS + # turtle_request + turtle_spawn + turtle_circle + clear_turtles + turtle_service + turtle_pub + turtle_action_server + # turtle_action_client + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + +#install launch file +install(DIRECTORY launch + DESTINATION share/${PROJECT_NAME} + ) + +#install configuration file (yaml files) for param server +install(DIRECTORY + config + DESTINATION share/${PROJECT_NAME} + ) + +ament_package() \ No newline at end of file diff --git a/software_training_assignment/action/Software.action b/software_training_assignment/action/Software.action new file mode 100644 index 0000000..2fbd125 --- /dev/null +++ b/software_training_assignment/action/Software.action @@ -0,0 +1,15 @@ +# Goal Request /moving_turtle/cmd_vel +float32 x_pos +float32 y_pos + +# Result - time +--- +uint64 duration + +--- +# feedback distance +# float32 dist +float32 dist + + + diff --git a/software_training_assignment/include/software_training_assignment/action_client.hpp b/software_training_assignment/include/software_training_assignment/action_client.hpp new file mode 100644 index 0000000..d0ee219 --- /dev/null +++ b/software_training_assignment/include/software_training_assignment/action_client.hpp @@ -0,0 +1,61 @@ +#ifndef ACTION_CLIENT_HPP_ +#define ACTION_CLIENT_HPP_ + +#include +#include +#include +#include + +#include +#include // ros2 time header +#include // ros2 action header +#include +#include + +#include // cmd_vel publisher message +#include // header for message to get moving turt position + +namespace composition +{ + + class action_client : public rclcpp::Node + { + + public: + SOFTWARE_TRAINING_PUBLIC + explicit action_client(const rclcpp::NodeOptions &options); + + // nice to have to prevent lengthy repitive code + using GoalHandleActionClient = + rclcpp_action::ClientGoalHandle; + + // using SoftwareAction = software_training_assignment::action::Software; + + SOFTWARE_TRAINING_PUBLIC + void send_goal(); + private: + // action client + rclcpp_action::Client::SharedPtr + client; + + rclcpp::TimerBase::SharedPtr timer; + + // goal callback function + SOFTWARE_TRAINING_LOCAL + void goal_response_callback(GoalHandleActionClient::SharedPtr goal_handle); + + // cancel response callback function + SOFTWARE_TRAINING_LOCAL + void + feedback_callback(GoalHandleActionClient::SharedPtr, const std::shared_ptr feedback); + + // handle accepted callback function + SOFTWARE_TRAINING_LOCAL + void + result_callback(const GoalHandleActionClient::WrappedResult & result); + + }; + +} + +#endif // ACTION_CLIENT_HPP_ diff --git a/software_training_assignment/include/software_training_assignment/action_turtle.hpp b/software_training_assignment/include/software_training_assignment/action_turtle.hpp new file mode 100644 index 0000000..93ed592 --- /dev/null +++ b/software_training_assignment/include/software_training_assignment/action_turtle.hpp @@ -0,0 +1,73 @@ +#ifndef ACTION_TURTLE_HPP_ +#define ACTION_TURTLE_HPP_ + +#include +#include +#include +#include + +#include +#include // ros2 time header +#include // ros2 action header +#include +#include + +#include // cmd_vel publisher message +#include // header for message to get moving turt position + +namespace composition{ + +class action_turtle : public rclcpp::Node { + +public: + SOFTWARE_TRAINING_PUBLIC + explicit action_turtle(const rclcpp::NodeOptions &options); + + // nice to have to prevent lengthy repitive code + using GoalHandleActionServer = + rclcpp_action::ServerGoalHandle; + +private: + // action server + rclcpp_action::Server::SharedPtr + action_server; + + // publisher to publish moving turtle commands + rclcpp::Publisher::SharedPtr publisher; + + // subscriber to get moving turt posiiton + rclcpp::Subscription::SharedPtr subscriber; + + // goal callback function + SOFTWARE_TRAINING_LOCAL + rclcpp_action::GoalResponse handle_goal( + const rclcpp_action::GoalUUID &uuid, + std::shared_ptr goal); + + // cancel response callback function + SOFTWARE_TRAINING_LOCAL + rclcpp_action::CancelResponse + handle_cancel(const std::shared_ptr goal_handle); + + // handle accepted callback function + SOFTWARE_TRAINING_LOCAL + void + handle_accepted(const std::shared_ptr goal_handle); + + SOFTWARE_TRAINING_LOCAL + // executioner callback function + void execute(const std::shared_ptr goal_handle); + + // for subscriber + float x = 0.0f; + float y = 0.0f; + float theta = 0.0f; + float linear_velocity = 0.0f; + float angular_velocity = 0.0f; + + static constexpr unsigned int QUEUE{10}; +}; + +} + +#endif // ACTION_TURTLE_HPP_ diff --git a/software_training_assignment/include/software_training_assignment/clear_turtles.hpp b/software_training_assignment/include/software_training_assignment/clear_turtles.hpp new file mode 100644 index 0000000..ed6951a --- /dev/null +++ b/software_training_assignment/include/software_training_assignment/clear_turtles.hpp @@ -0,0 +1,39 @@ +#ifndef CLEAR_TURTLES_HPP_ +#define CLEAR_TURTLES_HPP_ + +#include +#include +#include +#include + +#include + +#include +#include + +namespace composition +{ + + class clear_turtles : public rclcpp::Node + { + public: + SOFTWARE_TRAINING_PUBLIC + explicit clear_turtles(const rclcpp::NodeOptions &options); + + private: + rclcpp::Client::SharedPtr client; + rclcpp::TimerBase::SharedPtr timer; + + // all the turtles + std::vector turtle_names = {"moving_turtle", + "stationary_turtle"}; + + // one turtle + // std::vector turtle_names = {"turtle1"}; + + SOFTWARE_TRAINING_LOCAL + void kill(); + }; + +} // namespace composition +#endif // CLEAR_TURTLES_HPP_ \ No newline at end of file diff --git a/software_training_assignment/include/software_training_assignment/reset_moving_turtle.hpp b/software_training_assignment/include/software_training_assignment/reset_moving_turtle.hpp new file mode 100644 index 0000000..0ebd3bd --- /dev/null +++ b/software_training_assignment/include/software_training_assignment/reset_moving_turtle.hpp @@ -0,0 +1,48 @@ +#ifndef RESET_MOVING_TURTLE_HPP_ +#define RESET_MOVING_TURTLE_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +namespace composition +{ + + class reset_moving_turtle : public rclcpp::Node + { + public: + SOFTWARE_TRAINING_PUBLIC + explicit reset_moving_turtle(const rclcpp::NodeOptions &options); + + private: + rclcpp::Client::SharedPtr client; + rclcpp::TimerBase::SharedPtr timer; + // create service that will reset turtle to starting point + rclcpp::Service::SharedPtr service; + + + std::string turtle_to_move = "moving_turtle"; + float x_coord = 24.0f; + float y_coord = 10.0f; + float theta_coord = 0.0F; + + SOFTWARE_TRAINING_LOCAL + void service_callback( + const std::shared_ptr request, + std::shared_ptr response); + }; + +} // namespace composition +#endif // RESET_MOVING_TURTLE_HPP_ \ No newline at end of file diff --git a/software_training_assignment/include/software_training_assignment/spawn_turtle_nodelet.hpp b/software_training_assignment/include/software_training_assignment/spawn_turtle_nodelet.hpp new file mode 100644 index 0000000..6dd5c0a --- /dev/null +++ b/software_training_assignment/include/software_training_assignment/spawn_turtle_nodelet.hpp @@ -0,0 +1,47 @@ +#ifndef SPAWN_TURTLE_NODELET_HPP_ +#define SPAWN_TURTLE_NODELET_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace composition { + +class spawn_turtle_nodelet : public rclcpp::Node { + +public: + SOFTWARE_TRAINING_PUBLIC + explicit spawn_turtle_nodelet(const rclcpp::NodeOptions &options); + +private: + rclcpp::Client::SharedPtr client; + rclcpp::TimerBase::SharedPtr timer; + + SOFTWARE_TRAINING_LOCAL + void spawn_turtle(); + + static const unsigned int NUMBER_OF_TURTLES{2}; + + typedef struct turtle_info { + float x_pos; + float y_pos; + float rad; + } turtle_info; + + std::vector turtle_names{"stationary_turtle", "moving_turtle"}; + std::vector turtle_bio{{.x_pos = 5, .y_pos = 5, .rad = 0}, + {.x_pos = 25, .y_pos = 10, .rad = 0}}; + + // map of turtle name to turtle information + std::map turtle_description; +}; + +} // namespace composition + +#endif // SPAWN_TURTLE_NODELET_HPP_ diff --git a/software_training_assignment/include/software_training_assignment/turtle_circle_publisher.hpp b/software_training_assignment/include/software_training_assignment/turtle_circle_publisher.hpp new file mode 100644 index 0000000..b123e78 --- /dev/null +++ b/software_training_assignment/include/software_training_assignment/turtle_circle_publisher.hpp @@ -0,0 +1,54 @@ + +#ifndef TURTLE_CIRCLE_PUBLISHER_HPP_ +#define TURTLE_CIRCLE_PUBLISHER_HPP_ + +#include +#include +#include +#include + +#include + +#include +#include + +namespace composition { + +class turtle_circle_publisher : public rclcpp::Node { + +public: + SOFTWARE_TRAINING_PUBLIC + explicit turtle_circle_publisher(const rclcpp::NodeOptions &options); + +private: + // publisher + rclcpp::Publisher::SharedPtr publisher; + + // callback timer + rclcpp::TimerBase::SharedPtr timer; + + // set quality of service depth - AKA a backlog + static constexpr unsigned int QUEUE{10}; + + // namespace for values + typedef struct point { + + typedef struct linear { + static constexpr float x = 2; + static constexpr float y = 0; + static constexpr float z = 0; + } linear; + + typedef struct angular { + static constexpr float x = 0; + static constexpr float y = 0; + static constexpr float z = 1; + + } angular; + + } coordinates; +}; + +} // namespace composition + +#endif // TURTLE_CIRCLE_PUBLISHER_HPP_ \ No newline at end of file diff --git a/software_training_assignment/include/software_training_assignment/turtle_publisher.hpp b/software_training_assignment/include/software_training_assignment/turtle_publisher.hpp new file mode 100644 index 0000000..9327bae --- /dev/null +++ b/software_training_assignment/include/software_training_assignment/turtle_publisher.hpp @@ -0,0 +1,44 @@ +#ifndef TURTLE_PUBLISHER_HPP_ +#define TURTLE_PUBLISHER_HPP_ + +#include + +#include + +#include +#include +#include + +namespace composition { + +class turtle_publisher : public rclcpp::Node { +public: + explicit turtle_publisher(const rclcpp::NodeOptions &options); + +private: + // position subscribers + rclcpp::Subscription::SharedPtr stationary_turt_sub; + rclcpp::Subscription::SharedPtr moving_turt_sub; + + // turtle publisher with custom message + rclcpp::Publisher::SharedPtr publisher; + + // timer for publisher callback + rclcpp::TimerBase::SharedPtr timer; + + // callback groups - really just threads to run callbacks + rclcpp::CallbackGroup::SharedPtr callbacks; + + float x_stationary_turt; + float y_stationary_turt; + + float x_moving_turt; + float y_moving_turt; + + float total_distance; + + static const unsigned int QUEUE{10}; +}; +} // namespace composition + +#endif // TURTLE_PUBLISHER_HPP_ \ No newline at end of file diff --git a/software_training_assignment/include/software_training_assignment/visibility.h b/software_training_assignment/include/software_training_assignment/visibility.h new file mode 100644 index 0000000..907a909 --- /dev/null +++ b/software_training_assignment/include/software_training_assignment/visibility.h @@ -0,0 +1,65 @@ +// Copyright 2016 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOFTWARE_TRAINING__VISIBILITY_H_ +#define SOFTWARE_TRAINING__VISIBILITY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + +#ifdef __GNUC__ +#define SOFTWARE_TRAINING_EXPORT __attribute__((dllexport)) +#define SOFTWARE_TRAINING_IMPORT __attribute__((dllimport)) +#else +#define SOFTWARE_TRAINING_EXPORT __declspec(dllexport) +#define SOFTWARE_TRAINING_IMPORT __declspec(dllimport) +#endif + +#ifdef SOFTWARE_TRAINING_DLL +#define SOFTWARE_TRAINING_PUBLIC SOFTWARE_TRAINING_EXPORT +#else +#define SOFTWARE_TRAINING_PUBLIC SOFTWARE_TRAINING_IMPORT +#endif + +#define SOFTWARE_TRAINING_PUBLIC_TYPE SOFTWARE_TRAINING_PUBLIC + +#define SOFTWARE_TRAINING_LOCAL + +#else + +#define SOFTWARE_TRAINING_EXPORT __attribute__((visibility("default"))) +#define SOFTWARE_TRAINING_IMPORT + +#if __GNUC__ >= 4 +#define SOFTWARE_TRAINING_PUBLIC __attribute__((visibility("default"))) +#define SOFTWARE_TRAINING_LOCAL __attribute__((visibility("hidden"))) +#else +#define SOFTWARE_TRAINING_PUBLIC +#define SOFTWARE_TRAINING_LOCAL +#endif + +#define SOFTWARE_TRAINING_PUBLIC_TYPE +#endif + +#ifdef __cplusplus +} +#endif + +#endif // SOFTWARE_TRAINING__VISIBILITY_H_ \ No newline at end of file diff --git a/software_training_assignment/launch/testlaunch.py b/software_training_assignment/launch/testlaunch.py new file mode 100644 index 0000000..18be044 --- /dev/null +++ b/software_training_assignment/launch/testlaunch.py @@ -0,0 +1,147 @@ +import launch +import launch_ros.actions +from launch_ros.actions import Node +from launch_ros.descriptions import ComposableNode +from launch_ros.actions import ComposableNodeContainer +from launch import LaunchDescription +from launch.actions import (DeclareLaunchArgument, EmitEvent, ExecuteProcess, + LogInfo, RegisterEventHandler, TimerAction) +from launch.conditions import IfCondition +from launch.event_handlers import (OnExecutionComplete, OnProcessExit, + OnProcessIO, OnProcessStart, OnShutdown) +from launch.events import Shutdown +from launch.substitutions import (EnvironmentVariable, FindExecutable, + LaunchConfiguration, LocalSubstitution, + PythonExpression) + + + +def generate_launch_description(): + + + clear_turtle_container = ComposableNodeContainer( + name='clear_turtle_container', + namespace='ctc', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + ComposableNode( + package='software_training_assignment', + plugin='composition::clear_turtles', + name='clear_turtles', + ) + ], + output='screen', + ) + spawn_turtle_container = ComposableNodeContainer( + name='spawn_turtle_container', + namespace='stc', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + ComposableNode( + package='software_training_assignment', + plugin='composition::spawn_turtle_nodelet', + name='turtle_spawn', + ), + ], + output='screen', + ) + + + + + + continuous_node_container = ComposableNodeContainer( + name='node_container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + ComposableNode( + package='software_training_assignment', + plugin='composition::turtle_circle_publisher', + name='turtle_circle', + ), + ComposableNode( + package='software_training_assignment', + plugin='composition::turtle_publisher', + name='turtle_publisher', + ), + ComposableNode( + package='software_training_assignment', + plugin='composition::reset_moving_turtle', + name='reset_moving_turtle', + ), + ComposableNode( + package='software_training_assignment', + plugin='composition::action_turtle', + name='action_turtle', + ), + ], + output='screen', + ) + start_turtlesim = launch_ros.actions.Node( + package='turtlesim', + executable='turtlesim_node', + name='sim', + output="screen" + ) + + + action_location = " software_training_assignment/action/Software" + action_name = " /action_turtle" + # ros2 action send_goal /action_turtle software_training_assignment/action/Software "{x_pos: 5 , y_pos: 5} + call_action = ExecuteProcess( + cmd=[[ + FindExecutable(name='ros2'), + ' action send_goal ', + action_name, + action_location, + ' " {x_pos: 5 , y_pos: 5}"' + ]], + shell=True + ) + # ros2 service call /reset_moving_turtle software_training_assignment/srv/ResetMovingTurtle + service_name = " /reset_moving_turtle" + service_location = " software_training_assignment/srv/ResetMovingTurtle" + call_service = ExecuteProcess( + cmd=[[ + FindExecutable(name='ros2'), + ' service call', + service_name, + service_location + ]], + shell=True + ) + + + on_turtlesim_start = RegisterEventHandler( + OnProcessStart( + target_action=start_turtlesim, + on_start = clear_turtle_container + ) + ) + on_clear_complete = RegisterEventHandler( + OnExecutionComplete( + target_action=clear_turtle_container, + on_completion=spawn_turtle_container + ) + ) + on_spawn_complete = RegisterEventHandler( + OnExecutionComplete( + target_action=spawn_turtle_container, + on_completion=[continuous_node_container,TimerAction(period=3.0,actions=[call_action]),TimerAction(period=8.0,actions=[call_service])] + ) + ) + + ''' + 1. Launch turtlesim node. + 2. On turtlesim node process start, start clear turtle node + 3. On clear turtlenode execution complete, start spawn turtle node + 4. On spawn turtle node execution complete, start up all other nodes simultaneously + 5. After 3 seconds (using TimerAction), send action to move moving turtle to point + 6. After 8 seconds, reset moving turtle with service call + ''' + + return launch.LaunchDescription([start_turtlesim,on_turtlesim_start,on_clear_complete,on_spawn_complete]) diff --git a/software_training_assignment/launch/turtlelaunch.py b/software_training_assignment/launch/turtlelaunch.py new file mode 100644 index 0000000..b86eb0d --- /dev/null +++ b/software_training_assignment/launch/turtlelaunch.py @@ -0,0 +1,97 @@ +import launch +from launch_ros.actions import ComposableNodeContainer +from launch_ros.descriptions import ComposableNode + +from launch_ros.actions import Node +from launch_ros.descriptions import ComposableNode +from launch_ros.actions import ComposableNodeContainer +from launch import LaunchDescription +from launch.actions import (DeclareLaunchArgument, EmitEvent, ExecuteProcess, + LogInfo, RegisterEventHandler, TimerAction) +from launch.conditions import IfCondition +from launch.event_handlers import (OnExecutionComplete, OnProcessExit, + OnProcessIO, OnProcessStart, OnShutdown) +from launch.events import Shutdown +from launch.substitutions import (EnvironmentVariable, FindExecutable, + LaunchConfiguration, LocalSubstitution, + PythonExpression) + + +def generate_launch_description(): + """Generate launch description with multiple components.""" + + container = ComposableNodeContainer( + name='node_container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + ComposableNode( + package='software_training_assignment', + plugin='composition::clear_turtles', + name='clear_turtles', + ), + ComposableNode( + package='software_training_assignment', + plugin='composition::spawn_turtle_nodelet', + name='turtle_spawn', + ), + ComposableNode( + package='software_training_assignment', + plugin='composition::turtle_circle_publisher', + name='turtle_circle', + ), + ComposableNode( + package='software_training_assignment', + plugin='composition::turtle_publisher', + name='turtle_publisher', + ), + ComposableNode( + package='software_training_assignment', + plugin='composition::reset_moving_turtle', + name='reset_moving_turtle', + ), + ], + output='screen', + ) + turtlesimlaunch = ComposableNodeContainer( + package='turtlesim', + namespace='turtlesim1', + executable='turtlesim_node', + name='turtle1', + composable_node_descriptions=[] + ) + + # spawn_turtle = ExecuteProcess( + # cmd=[[ + # FindExecutable(name='ros2'), + # ' service call ', + # turtlesim_ns, + # '/spawn ', + # 'turtlesim/srv/Spawn ', + # '"{x: 2, y: 2, theta: 0.2}"' + # ]], + # shell=True + # ) + + # task1 = RegisterEventHandler( + # OnProcessStart( + # target_action=turtlesim_node, + # on_start=[ + # LogInfo(msg='Turtlesim started, spawning turtle'), + # spawn_turtle + # ] + # ) + # ) + # task2 = RegisterEventHandler( + # OnProcessStart( + # target_action=turtlesim_node, + # on_start=[ + # LogInfo(msg='Turtlesim started, spawning turtle'), + # spawn_turtle + # ] + # ) + # ), + + + return launch.LaunchDescription([container,turtlesimlaunch]) \ No newline at end of file diff --git a/software_training_assignment/msg/Software.msg b/software_training_assignment/msg/Software.msg new file mode 100644 index 0000000..9e3c23b --- /dev/null +++ b/software_training_assignment/msg/Software.msg @@ -0,0 +1,3 @@ +float64 x_pos +float64 y_pos +float64 distance \ No newline at end of file diff --git a/software_training_assignment/package.xml b/software_training_assignment/package.xml new file mode 100644 index 0000000..26a20d5 --- /dev/null +++ b/software_training_assignment/package.xml @@ -0,0 +1,43 @@ + + + + software_training_assignment + 0.0.0 + TODO: Package description + keyonjerome + TODO: License declaration + + ament_cmake + + + rclcpp_components + rclcpp + std_msgs + turtlesim + rcutils + geometry_msgs + builtin_interfaces + rosidl_default_generators + rclcpp_action + + + rclcpp + rclcpp_components + launch_ros + std_msgs + rcutils + turtlesim + geometry_msgs + rosidl_default_runtime + rclcpp_action + action_msgs + + ament_lint_auto + ament_lint_common + + rosidl_interface_packages + + + ament_cmake + + diff --git a/software_training_assignment/src/action_client.cpp b/software_training_assignment/src/action_client.cpp new file mode 100644 index 0000000..7332bbd --- /dev/null +++ b/software_training_assignment/src/action_client.cpp @@ -0,0 +1,111 @@ +#include +// #include +#include +#include +using namespace std::placeholders; +using namespace std::literals::chrono_literals; + +namespace composition +{ + + action_client::action_client(const rclcpp::NodeOptions &options) + : Node("action_client", options) + { + + this->client = this->create_client(this->get_node_base_interface(), + this->get_node_graph_interface(), + this->get_node_logging_interface(), + this->get_node_waitables_interface(), "/action_client"); + this->timer = this->create_wall_timer( + std::chrono::milliseconds(5000), + std::bind(&action_client::send_goal, this)); + }; + + void action_client::send_goal() + { + + using namespace std::placeholders; + + this->timer->cancel(); + + if (!this->client->wait_for_action_server(std::chrono::seconds(10))) + { + RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting"); + rclcpp::shutdown(); + + return; + } + + auto goal_msg = software_training_assignment::action::Software::Goal(); + + RCLCPP_INFO(this->get_logger(), "Sending goal"); + + auto send_goal_options = rclcpp_action::Client::SendGoalOptions(); + + send_goal_options.goal_response_callback = + std::bind(&action_client::goal_response_callback, this, _1); + + send_goal_options.feedback_callback = + std::bind(&action_client::feedback_callback, this, _1, _2); + + send_goal_options.result_callback = + std::bind(&action_client::result_callback, this, _1); + + this->client->async_send_goal(goal_msg, send_goal_options); + } + + void action_client::goal_response_callback(GoalHandleActionClient::SharedPtr goal_handle) + { + if (!goal_handle) + { + RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server"); + } + else + { + RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result"); + } + } + + void action_client::feedback_callback( + GoalHandleActionClient::SharedPtr, + const std::shared_ptr feedback) + { + std::stringstream ss; + ss << "Next number in sequence received: "; + for (auto number : feedback->partial_sequence) + { + ss << number << " "; + } + RCLCPP_INFO(this->get_logger(), "%s", ss.str().c_str()); + } + + void action_client::result_callback(const GoalHandleActionClient::WrappedResult &result) + { + switch (result.code) + { + case rclcpp_action::ResultCode::SUCCEEDED: + break; + case rclcpp_action::ResultCode::ABORTED: + RCLCPP_ERROR(this->get_logger(), "Goal was aborted"); + return; + case rclcpp_action::ResultCode::CANCELED: + RCLCPP_ERROR(this->get_logger(), "Goal was canceled"); + return; + default: + RCLCPP_ERROR(this->get_logger(), "Unknown result code"); + return; + } + std::stringstream ss; + ss << "Result received: "; + for (auto number : result.result->sequence) + { + ss << number << " "; + } + RCLCPP_INFO(this->get_logger(), "%s", ss.str().c_str()); + rclcpp::shutdown(); + } +} + +#include + +RCLCPP_COMPONENTS_REGISTER_NODE(composition::action_client) diff --git a/software_training_assignment/src/action_turtle.cpp b/software_training_assignment/src/action_turtle.cpp new file mode 100644 index 0000000..cf93a29 --- /dev/null +++ b/software_training_assignment/src/action_turtle.cpp @@ -0,0 +1,177 @@ + + +#include +#include + +#include + +using namespace std::placeholders; +using namespace std::chrono_literals; + +namespace composition +{ + // on node creation, create publisher, action server, turtle pose subscriber... + action_turtle::action_turtle(const rclcpp::NodeOptions &options) : Node("action_turtle", options) + { + + this->action_server = rclcpp_action::create_server(this, "action_turtle", + std::bind(&action_turtle::handle_goal, this, _1, _2), + std::bind(&action_turtle::handle_cancel, this, _1), + std::bind(&action_turtle::handle_accepted, this, _1)); + + this->publisher = this->create_publisher( + "/moving_turtle/cmd_vel", rclcpp::QoS(QUEUE)); + + auto subscriber_callback = + [this](const turtlesim::msg::Pose::SharedPtr msg) -> void + { + this->action_turtle::x = msg->x; + this->action_turtle::y = msg->y; + this->action_turtle::theta = msg->theta; + this->action_turtle::linear_velocity = msg->linear_velocity; + this->action_turtle::angular_velocity = msg->angular_velocity; + // RCLCPP_INFO(this->get_logger(),"UPDATED X:%f || UPDATED Y:%f",this->action_turtle::x,this->action_turtle::y); + }; + + // create subscriber + this->subscriber = this->create_subscription( + "/moving_turtle/pose", QUEUE, subscriber_callback); + } + + rclcpp_action::CancelResponse action_turtle::handle_cancel( + const std::shared_ptr goal_handle) + { + RCLCPP_INFO(this->get_logger(), "Received request to cancel goal"); + (void)goal_handle; + return rclcpp_action::CancelResponse::ACCEPT; + } + + // respond to goal request + rclcpp_action::GoalResponse action_turtle::handle_goal( + const rclcpp_action::GoalUUID &uuid, + std::shared_ptr goal) + { + + (void)uuid; // not needed - prevents compiler warnings/errors -Wall flag + RCLCPP_INFO(this->get_logger(), "Goal Received"); + RCLCPP_INFO(this->get_logger(), "linear X:%f Y:%f", goal->x_pos, + goal->y_pos); + // accept zee goal me friendo. COMPLETE ZEE GOAL! + return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; + } + + void action_turtle::handle_accepted(const std::shared_ptr goal_handle) + { + // using namespace std::placeholders; + // this needs to return quickly to avoid blocking the executor, so spin up a new thread + std::thread{std::bind(&action_turtle::execute, this, _1), goal_handle}.detach(); + } + + /* + # Feedback /moving_turtle/pose + float32 x_pos + float32 y_pos + float32 theta_pos + */ + + // executioner callback function + void action_turtle::execute(const std::shared_ptr goal_handle) + { + + RCLCPP_INFO(this->get_logger(), "Executing goal"); + rclcpp::Rate loop_rate(1); + + + rclcpp::Time start_time = this->now(); // get the current time + + + const float time_constant = 1.250f; + // get goal data for later + const auto goal = goal_handle->get_goal(); + + // create result + std::unique_ptr result = + std::make_unique(); + + + auto goal_x = goal->x_pos; + auto goal_y = goal->y_pos; + + // create reference to current information for ease of use + float &curr_x = this->action_turtle::x; + float &curr_y = this->action_turtle::y; + // float &curr_theta = this->action_turtle::theta; + + + + float TOL = 0.25; + + // Have you heard of our lord and saviour, v = d/t ? + float vel_x = (goal_x - curr_x)/time_constant; + float vel_y = (goal_y - curr_y)/time_constant; + + RCLCPP_INFO(this->get_logger(),"VEL_X:%f, VEL_Y:%f", vel_x, vel_y); + + + RCLCPP_INFO(this->get_logger(),"CURR_X:%f, CURR_Y:%f || GOAL_X: %f, GOAL_Y: %f",curr_x,curr_y,goal_x,goal_y); + while(rclcpp::ok() && ( + !(curr_x >= goal_x - TOL && curr_x <= goal_x + TOL) || !(curr_y >= goal_y - TOL && curr_y <= goal_y + TOL)) + ) { + auto message = std::make_unique(); + message->linear.x = vel_x; + message->linear.y = vel_y; + message->linear.z = 0.0f; + message->angular.x = 0.0f; + message->angular.y = 0.0f; + message->angular.z = 0.0f; + + // create feedback + std::unique_ptr feedback = + std::make_unique(); + + feedback->dist = sqrt(pow(goal->x_pos - curr_x,2)+ pow(goal->y_pos - curr_y,2)); + + this->publisher->publish(std::move(message)); + + goal_handle->publish_feedback(std::move(feedback)); + + RCLCPP_INFO(this->get_logger(),"CURR_X:%f, CURR_Y:%f || GOAL_X: %f, GOAL_Y: %f",curr_x,curr_y,goal_x,goal_y); + RCLCPP_INFO(this->get_logger(),"CMD_VEL_X:%f, CMD_VEL_Y:%f",vel_x,vel_y); + + // loop_rate.sleep(); + } + + // if goal is done + if (rclcpp::ok()) { + + auto message = std::make_unique(); + message->linear.x = 0.0f; + message->linear.y = 0.0f; + message->linear.z = 0.0f; + message->angular.x = 0.0f; + message->angular.y = 0.0f; + message->angular.z = 0.0f; + this->publisher->publish(std::move(message)); + + + rclcpp::Time end = this->now(); // get end time + rclcpp::Duration duration = end - start_time; // compute time taken + long int res_time{duration.nanoseconds()}; // should be uint64_t + + // fill in result + result->duration = res_time; + + // set the result + goal_handle->succeed( + std::move(result)); // move ownership so ptr is still usable + RCLCPP_INFO(this->get_logger(), "Finish Executing Goal"); + } + + + } + +} + +#include + +RCLCPP_COMPONENTS_REGISTER_NODE(composition::action_turtle) \ No newline at end of file diff --git a/software_training_assignment/src/clear_turtles.cpp b/software_training_assignment/src/clear_turtles.cpp new file mode 100644 index 0000000..7aa193a --- /dev/null +++ b/software_training_assignment/src/clear_turtles.cpp @@ -0,0 +1,55 @@ +#include + +using namespace std::literals::chrono_literals; + +namespace composition { + + clear_turtles::clear_turtles(const rclcpp::NodeOptions &options) : Node("clear_turtles",options) { + + // create client + client = this->create_client("/kill"); + + // create callback + // timer = this->create_wall_timer(2s, std::bind(&clear_turtles::kill,this)); + clear_turtles::kill(); + } + + // kill all turtles + void clear_turtles::kill() { + + // check if service exists + if (!client->wait_for_service(2s)) { + if (!rclcpp::ok()) { + RCLCPP_ERROR(this->get_logger(), + "Interrupted while waiting for service. Exiting!"); + return; + } + RCLCPP_INFO(this->get_logger(), "Service not available after waiting"); + return; + } + RCLCPP_INFO(this->get_logger(), "TURTLES KILLED."); + + for (std::string &name : turtle_names) { + auto request = std::make_shared(); + request->name = name; + + // create callback to handle response because no 'spin()' is available + + auto callback = + [this](rclcpp::Client::SharedFuture response) + -> void { + (void)response; + RCLCPP_INFO(this->get_logger(), "Turtle Killed"); + rclcpp::shutdown(); // kill this node + }; + + auto result = client->async_send_request(request, callback); + } + + } + +} // namespace composition + +#include + +RCLCPP_COMPONENTS_REGISTER_NODE(composition::clear_turtles) \ No newline at end of file diff --git a/software_training_assignment/src/reset_moving_turtle.cpp b/software_training_assignment/src/reset_moving_turtle.cpp new file mode 100644 index 0000000..1e70a22 --- /dev/null +++ b/software_training_assignment/src/reset_moving_turtle.cpp @@ -0,0 +1,74 @@ +#include +#include + +using namespace std::literals::chrono_literals; + +using namespace std::placeholders; + +namespace composition +{ + + reset_moving_turtle::reset_moving_turtle(const rclcpp::NodeOptions &options) : Node("reset_moving_turtle", options) + { + + // I believe this only creates the service, but does not call it. + + // create client + // teleport_absolute may need to be TeleportAbsolute + this->client = this->create_client("/moving_turtle/teleport_absolute"); + + // // create callback + // timer = this->create_wall_timer(2s, std::bind(&reset_moving_turtle::reset_moving_turtle,this)); + + // create service + this->service = this->create_service("/reset_moving_turtle", std::bind(&reset_moving_turtle::service_callback, this, _1, _2)); + } + + void reset_moving_turtle::service_callback(const std::shared_ptr request, + std::shared_ptr response) + { + + (void)request; // request is not needed + + RCLCPP_INFO(this->get_logger(), "Starting reset_moving_turtle service..."); + + // wait until service is available + if (!client->wait_for_service(1s)) + { + if (!rclcpp::ok()) + { + RCLCPP_ERROR(this->get_logger(), + "Interrupted while waiting for service. Exiting!"); + return; + } + RCLCPP_INFO(this->get_logger(), "Service not available after waiting"); + return; + } + + auto client_request = std::make_shared(); + client_request->x = reset_moving_turtle::x_coord; + client_request->y = reset_moving_turtle::y_coord; + client_request->theta = reset_moving_turtle::theta_coord; + + // create callback to handle response because no 'spin()' is available + + auto response_callback = + [this](rclcpp::Client::SharedFuture teleport_response) + -> void + { + (void)teleport_response; + RCLCPP_INFO(this->get_logger(), "Moving turtle reset completed."); + // rclcpp::shutdown(); // need this or else will keep on executing callback - + // // only want to execute once! + }; + + auto result = client->async_send_request(client_request, response_callback); + + response->success = true; + } + +} // namespace composition + +#include + +RCLCPP_COMPONENTS_REGISTER_NODE(composition::reset_moving_turtle) \ No newline at end of file diff --git a/software_training_assignment/src/spawn_turtle_nodelet.cpp b/software_training_assignment/src/spawn_turtle_nodelet.cpp new file mode 100644 index 0000000..8b6957c --- /dev/null +++ b/software_training_assignment/src/spawn_turtle_nodelet.cpp @@ -0,0 +1,70 @@ +#include +#include + +using namespace std::literals::chrono_literals; + +namespace composition { + +spawn_turtle_nodelet::spawn_turtle_nodelet(const rclcpp::NodeOptions &options) + : Node("spawn_turtle_nodelet") { + + client = this->create_client("/spawn"); + + // this should only run once, so remove + // timer = this->create_wall_timer(2s, std::bind(&spawn_turtle_nodelet::spawn_turtle, this)); + + for(size_t i(0); i < NUMBER_OF_TURTLES; i++ ) { + turtle_description.insert({turtle_names[i],turtle_bio[i]}); + } + + spawn_turtle_nodelet::spawn_turtle(); + // timer = create_wall_timer(2s,std::bind(&spawn_turtle_nodelet::spawn_turtle,this)); + }; + +void spawn_turtle_nodelet::spawn_turtle() { + + if (!client->wait_for_service(2s)) { + if (!rclcpp::ok()) { + RCLCPP_ERROR(this->get_logger(), "Service was interupted - EXIT"); + return; + } + + RCLCPP_INFO(this->get_logger(), "Waiting for service - DNE - EXIT"); + return; + } + + // only reaches this point if a "/spawn" service exists + RCLCPP_INFO(this->get_logger(), "SPAWN-TURTLE-SUCCESSFUL"); + for(const std::string &name : turtle_names) { + + // create request + std::unique_ptr request = + std::make_unique(); + + // fill in repsonse + request->name = name; + request->x = turtle_description[name].x_pos; + request->y = turtle_description[name].y_pos; + request->theta = turtle_description[name].rad; + + // create a callback to call client and because no 'spin()' is available + auto callback = + [this](rclcpp::Client::SharedFuture response) + -> void { + RCLCPP_INFO(this->get_logger(), "Turtle Created: %s", + response.get()->name.c_str()); + // we only want this spawn_turtle() stuff to run once, when the node is created, so there's no need to keep it running once spawn_turtle() has finished + rclcpp::shutdown(); + }; + + // send request + auto result = client->async_send_request(std::move(request), callback); + } + + } +} + + +#include + +RCLCPP_COMPONENTS_REGISTER_NODE(composition::spawn_turtle_nodelet) diff --git a/software_training_assignment/src/turtle_circle_publisher.cpp b/software_training_assignment/src/turtle_circle_publisher.cpp new file mode 100644 index 0000000..efc43a0 --- /dev/null +++ b/software_training_assignment/src/turtle_circle_publisher.cpp @@ -0,0 +1,40 @@ +#include +#include + +using namespace std::literals::chrono_literals; + +namespace composition { + +turtle_circle_publisher::turtle_circle_publisher(const rclcpp::NodeOptions &options) + : Node("turtle_circle_publisher") { + + + auto publisher_callback = [this](void) -> void { + auto message = std::make_unique(); + message->linear.x = turtle_circle_publisher::coordinates::linear::x; + message->linear.y = turtle_circle_publisher::coordinates::linear::y; + message->linear.z = turtle_circle_publisher::coordinates::linear::z; + + message->angular.x = turtle_circle_publisher::coordinates::angular::x; + message->angular.y = turtle_circle_publisher::coordinates::angular::y; + message->angular.z = turtle_circle_publisher::coordinates::angular::z; + + this->publisher->publish(std::move(message)); + }; + + // create publisher + // create a Quality of Service object with a history/ depth of 10 calls + this->publisher = this->create_publisher( + "/turtle1/cmd_vel", rclcpp::QoS(QUEUE)); + + + // publish values every 1ms + this->timer = this->create_wall_timer(1ms, publisher_callback); + }; + + +} + +#include + +RCLCPP_COMPONENTS_REGISTER_NODE(composition::turtle_circle_publisher) diff --git a/software_training_assignment/src/turtle_publisher.cpp b/software_training_assignment/src/turtle_publisher.cpp new file mode 100644 index 0000000..79abcc9 --- /dev/null +++ b/software_training_assignment/src/turtle_publisher.cpp @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +namespace composition { + +turtle_publisher::turtle_publisher(const rclcpp::NodeOptions &options) + : Node("turtle_publisher", options) { + + // callback for stationary turtle position + auto stationary_turt_callback = + [this](const turtlesim::msg::Pose::SharedPtr msg) -> void { + this->x_stationary_turt = msg->x; + this->y_stationary_turt = msg->y; + }; + + // callback for moving turtle position + auto moving_turt_callback = + [this](const turtlesim::msg::Pose::SharedPtr msg) -> void { + this->x_moving_turt = msg->x; + this->y_moving_turt = msg->y; + }; + + // publisher callback + auto publisher_callback = [this](void) -> void { + // compute absolute difference in coordinates + double position_x{abs(this->x_stationary_turt - this->x_moving_turt)}; + double position_y{abs(this->y_stationary_turt - this->y_moving_turt)}; + + // create message to publish + auto msg = std::make_unique(); + msg->x_pos = position_x; + msg->y_pos = position_y; + // compute distance using trig + msg->distance = sqrt((position_x * position_x) + (position_y * position_y)); + + // publish message + this->publisher->publish( + std::move(msg)); // must move message because it will be outscope and be + // freed because it is a smart pointer we do not want + // that - need to keep memory. + }; + + // create callback group - really threads that allow things to be executed on + // that thread + callbacks = + this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); + + // create callback thread - so that everything with this callback will be on + // one thread - this will ensure mutual exclusion + auto callback_option = rclcpp::SubscriptionOptions(); + callback_option.callback_group = callbacks; + + // set ros2 topic statistics + callback_option.topic_stats_options.state = + rclcpp::TopicStatisticsState::Enable; + + // set custom name for this topic statistic + std::string s_name("/statistics/"); + std::string node_name(this->get_name()); + + // topic name will be '/statistics/turtle_publisher' -- naming conventions + // makes it easier to figure which statistics is for which node/component + std::string stat_name = s_name + node_name; + callback_option.topic_stats_options.publish_topic = stat_name.c_str(); + + // instantiate subscribers + stationary_turt_sub = this->create_subscription( + "/stationary_turtle/pose", QUEUE, stationary_turt_callback, + callback_option); + + moving_turt_sub = this->create_subscription( + "/moving_turtle/pose", QUEUE, moving_turt_callback, callback_option); + + // instantiate publisher + publisher = this->create_publisher( + "/difference", QUEUE); + + // set timer for publisher + timer = this->create_wall_timer(5ms, publisher_callback, callbacks); + + // subscriber callbacks +} + +} // namespace composition + +#include + +RCLCPP_COMPONENTS_REGISTER_NODE(composition::turtle_publisher) \ No newline at end of file diff --git a/software_training_assignment/srv/ResetMovingTurtle.srv b/software_training_assignment/srv/ResetMovingTurtle.srv new file mode 100644 index 0000000..e2c59ee --- /dev/null +++ b/software_training_assignment/srv/ResetMovingTurtle.srv @@ -0,0 +1,3 @@ + +--- +bool success \ No newline at end of file