diff --git a/docs/General ROS 2/Building ROS 2 Packages/Message Types.md b/docs/General ROS 2/Building ROS 2 Packages/Message Types.md deleted file mode 100755 index 4fc5c2b..0000000 --- a/docs/General ROS 2/Building ROS 2 Packages/Message Types.md +++ /dev/null @@ -1,28 +0,0 @@ -# Message Types -## What is a Message Type -In ROS 2 (Robot Operating System 2), a `msg` type refers to a message definition that contains data structures used for communication between nodes. These messages are the building blocks for publishing and subscribing to topics, which enable nodes to exchange information with each other. Think of a message as a container that holds specific types and amounts of data, like a digital package being sent between nodes in a ROS 2 nodes. Each type of message corresponds to a particular set of data fields and their data types (e.g., `int8`, `float32`, `String`). When you define a message, you're specifying the structure of the data that will be sent or received by nodes. This allows for more organized and efficient communication between nodes. -## How to find them -When creating a publisher you have to figure out what data type it is going to publish. To find examples of the different data types in ROS 2 you can run a special command. - -```bash { .annotate } -ros2 interface show example_interfaces/msg/String #(1) -``` - -1. In this case it was the `String` type that I used but it could have been any other type. To get a list use `Tab`before typing `String` for a autocomplete list of options. - -## Including Message Types -Once you have found the message type you will need to make sure that it is imported. This can be pretty easily in both C++ and Python. - -Below you can see that we use the `String` as the message type. This example works for other types as well of messages as well. You can just swap `String` for whatever other data type that is available. -=== "Python" - ```python - from example_interfaces.msg import String - ``` - -=== "C++" - ``` c++ - #include "example_interfaces/msg/string.hpp" - ``` - - - diff --git a/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Custom Interfaces.md b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Custom Interfaces.md new file mode 100755 index 0000000..69829b4 --- /dev/null +++ b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Custom Interfaces.md @@ -0,0 +1,112 @@ +# ROS 2 Custom Interfaces + +## Creating Custom Interface +In ROS 2, a **custom interface** defines unique message, service, or action types tailored to specific application needs. It is important because it enables precise communication between nodes, ensuring data is structured and interpreted correctly for specialized robotics tasks. + +### Making Package +To create a custom interface it is best to make a dedicated package for all the interfaces that are needed in that project. Go to the workspace and navigate to the `src` folder. +```bash +ros2 pkg create {robot_name}_interfaces +``` + +Once the package is create open the package folder with visual studio code and delete the `include` directory and the `src` directory. Next open the `packages.xml` file and delete all the lines that have the `{somthing}`. Finally, add the following highlighted lines. + +```xml title="packages.xml" linenums="1" hl_lines="12-14" + + + + my_robot_interfaces + 0.0.0 + TODO: Package description + ros-laptop1 + TODO: License declaration + + ament_cmake + + rosidl_default_generators + rosidl_default_runtime + rosidl_interface_packages + + ament_lint_auto + ament_lint_common + + + ament_cmake + + +``` + +Now that the `packages.xml` is one the `CMakeLists.txt` file needs to be changed. Go through and delete everything so it looks like this and add the lines that are highlighted. +```cmake title="CMakeLists.txt" linenums="1" hl_lines="10 12-15 17" +cmake_minimum_required(VERSION 3.8) +project(my_robot_interfaces) + +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) + +rosidl_generate_interfaces(${PROJECT_NAME} + "msg/{service_name}.msg" + "srv/{service_name}.srv" +) + +ament_export_dependencies(rosidl_default_runtime) +ament_package() +``` + +Lines 13 and 14 are the custom interfaces that you created and want to build. Look at the next two sections to learn about how to create these files with the message and server configuration. + +### Creating a Custom Message +To create a custom message navigate to the package that was created in the [[#Making Package]] section. Here we will create a folder in this package called `msg` and inside this folder we will put all of are message configs. For example lets create a message for a color sensor. This message might contain the the red, green, and blue value from the sensor. +```xml title="ColorSensor.msg" linenums="1" +# you can add comments to your message configs like this +int64 red # comments can be added here also +int64 green +int64 blue +``` + +Now all you have to do add it to the `CMakeLists.txt` file and run `colcon build`. + +### Creating a Custom Service +To create a custom service navigate to the package that was created in the [[#Making Package]] section. Here we will create a folder in this package called `srv` and inside this folder we will put all of are service configs. For example lets create a service for a rectangular area calculator. This service might have a length and width for a request and as a response just an area. +```xml title="CalculateArea.srv" linenums="1" +# you can add comments to your message configs like this +# the --- seperates the request from the response +float64 length +float64 width +--- +float64 area +``` + +Now all you have to do add it to the `CMakeLists.txt` file and run `colcon build`. + +### Using a Custom Interface +To use a custom interface in a Python or C++ program import it or include it just like normal. If you have problems with vscode not recognizing it in the C++ programs you will have to edit the `.vscode/c_cpp_properties.json` file and add it to the include path. +```json title="c_cpp_properties.json" linenums="1" hl_lines="11" +{ + "configurations": [ + { + "browse": { + "databaseFilename": "${default}", + "limitSymbolsToIncludedHeaders": false + }, + "includePath": [ + "/opt/ros/humble/include/**", + "/usr/include/**", + "~/some/folder/{workspace_name}_ws/install/{robot_name}_interfaces/include/**" + ], + "name": "ROS", + "intelliSenseMode": "gcc-x64", + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu11", + "cppStandard": "c++14" + } + ], + "version": 4 +} +``` + diff --git a/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Interfaces.md b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Interfaces.md new file mode 100755 index 0000000..80065df --- /dev/null +++ b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Interfaces.md @@ -0,0 +1,58 @@ +# ROS 2 Interfaces +## What is a Message Type +In ROS 2 (Robot Operating System 2), a `msg` type refers to a message definition that contains data structures used for communication between nodes. These messages are the building blocks for publishing and subscribing to topics, which enable nodes to exchange information with each other. Think of a message as a container that holds specific types and amounts of data, like a digital package being sent between nodes in a ROS 2 nodes. Each type of message corresponds to a particular set of data fields and their data types (e.g., `int8`, `float32`, `String`). When you define a message, you're specifying the structure of the data that will be sent or received by nodes. This allows for more organized and efficient communication between nodes. +## How to find them +When creating a publisher you have to figure out what data type it is going to publish. To find examples of the different data types in ROS 2 you can run a special command. + +```bash { .annotate } +ros2 interface show example_interfaces/msg/String #(1) +``` + +1. In this case it was the `String` type that I used but it could have been any other type. To get a list use `Tab`before typing `String` for a autocomplete list of options. + +## Including Message Types +Once you have found the message type you will need to make sure that it is imported. This can be pretty easily in both C++ and Python. + +Below you can see that we use the `String` as the message type. This example works for other types as well of messages as well. You can just swap `String` for whatever other data type that is available. +=== "Python" + ```python + from example_interfaces.msg import String + ``` + +=== "C++" + ``` c++ + #include "example_interfaces/msg/string.hpp" + ``` + + +## Variables in ROS 2 Interfaces +You can find more information at the [ROS 2 Interface Documentation](https://docs.ros.org/en/rolling/Concepts/Basic/About-Interfaces.html). + +| Type name | [C++](https://design.ros2.org/articles/generated_interfaces_cpp.html) | [Python](https://design.ros2.org/articles/generated_interfaces_python.html) | [DDS type](https://design.ros2.org/articles/mapping_dds_types.html) | +| --------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| bool | bool | builtins.bool | boolean | +| byte | uint8_t | builtins.bytes* | octet | +| char | char | builtins.int* | char | +| float32 | float | builtins.float* | float | +| float64 | double | builtins.float* | double | +| int8 | int8_t | builtins.int* | octet | +| uint8 | uint8_t | builtins.int* | octet | +| int16 | int16_t | builtins.int* | short | +| uint16 | uint16_t | builtins.int* | unsigned short | +| int32 | int32_t | builtins.int* | long | +| uint32 | uint32_t | builtins.int* | unsigned long | +| int64 | int64_t | builtins.int* | long long | +| uint64 | uint64_t | builtins.int* | unsigned long long | +| string | std::string | builtins.str | string | +| wstring | std::u16string | builtins.str | wstring | + +_Every built-in-type can be used to define arrays:_ + +|Type name|[C++](https://design.ros2.org/articles/generated_interfaces_cpp.html)|[Python](https://design.ros2.org/articles/generated_interfaces_python.html)|[DDS type](https://design.ros2.org/articles/mapping_dds_types.html)| +|---|---|---|---| +|static array|std::array|builtins.list*|T[N]| +|unbounded dynamic array|std::vector|builtins.list|sequence| +|bounded dynamic array|custom_class|builtins.list*|sequence| +|bounded string|std::string|builtins.str*|string| + +All types that are more permissive than their ROS definition enforce the ROS constraints in range and length by software. \ No newline at end of file diff --git a/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Launch Files.md b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Launch Files.md new file mode 100755 index 0000000..bfd65e9 --- /dev/null +++ b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Launch Files.md @@ -0,0 +1,148 @@ +# ROS 2 Launch Files +**Launch files** are Python-based scripts that manage the startup of multiple nodes and configurations simultaneously. They allow users to define which nodes to launch, pass parameters, remap topics, and set arguments, streamlining the deployment and orchestration of complex robotics systems. + + +## Creating Launch File Package +When creating a launch file in ROS 2 you have to use the `ros2 pkg create package_name`. Once the package is created delete all the folders in the package folder and create a folder called `launch` inside this folder you can put all the launch files. Finally edit the `CMakeLists.txt` so that it looks like the one below. + +```cmake title="CMakeLists.txt" linenums="1" +cmake_minimum_required(VERSION 3.8) +project(my_robot1_description) + + +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) + +install( + DIRECTORY launch + DESTINATION share/${PROJECT_NAME}/ +) + +ament_package() +``` + +## Creating Python Launch File +Below is a example of a launch file. +=== "Python" + + ```python title="display.launch.py" linenums="1" + from launch import LaunchDescription + from launch_ros.parameter_descriptions import ParameterValue + from launch.substitutions import Command + from launch_ros.actions import Node + from ament_index_python import get_package_share_directory + import os + + + def generate_launch_description(): + urdf_path = os.path.join(get_package_share_directory('my_robot1_description'), 'urdf', 'my_robot1.urdf') + robot_description = ParameterValue(Command(['xacro ', urdf_path]), value_type=str) + rviz_config_path = os.path.join(get_package_share_directory('my_robot1_description'), 'rviz', 'urdf_config.rviz') + + + robot_state_publisher_node = Node( + package="robot_state_publisher", + executable="robot_state_publisher", + parameters = [{"robot_description": robot_description}] + ) + + + joint_state_publisher_gui_node = Node( + package="joint_state_publisher_gui", + executable="joint_state_publisher_gui", + ) + + + rviz2_node = Node( + package="rviz2", + executable="rviz2", + arguments=['-d', rviz_config_path] + ) + + return LaunchDescription([ + robot_state_publisher_node, + joint_state_publisher_gui_node, + rviz2_node + ]) + ``` + +=== "XML" + + ```xml title="display.launch.xml" linenums="1" + + + + + + + + + + + + + + ``` + +## Adding configs and UFDRs in Launch File Args +When you need to add a file or a config like when creating a rviz node you can take a few quick steps. First create another folder in the same directory you have the launch file. Next navigate to the `CMakeList.txt` file and and add a space followed by the name of the folder you create. In this case I am creating an folder called `rviz` and I will add it to the end of line `12`. + +```cmake title="CMakeLists.txt" linenums="1" hl_lines="12" +cmake_minimum_required(VERSION 3.8) +project(my_robot1_description) + +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) + +install( + DIRECTORY launch rviz + DESTINATION share/${PROJECT_NAME}/ +) + +ament_package() +``` + +Now once it is added you will need to reference it in you launch file. Doing this is slightly different in python and xml but it is not two hard. + +=== "Python" + + ```python + from launch import LaunchDescription + from launch_ros.actions import Node + from ament_index_python import get_package_share_directory + import os + + + def generate_launch_description(): + rviz_config_path = os.path.join(get_package_share_directory('my_robot1_description'), 'rviz', 'urdf_config.rviz') + + rviz2_node = Node( + package="rviz2", + executable="rviz2", + arguments=['-d', rviz_config_path] + ) + return LaunchDescription([rviz2_node]) + ``` + +=== "XML" + + ```xml + + + + + ``` + +## Using Launch File +To run a launch file use the `ros2 launch` command. +```bash +ros2 launch package_name file.launch.py +``` diff --git a/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Parameters.md b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Parameters.md new file mode 100755 index 0000000..b262f46 --- /dev/null +++ b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Parameters.md @@ -0,0 +1,105 @@ +# ROS 2 Parameters +## Overview of ROS 2 Parameters +In ROS 2, a **node parameter** is a configurable value used to adjust a node's behavior without modifying its code. Parameters are important because they provide flexibility, allowing dynamic tuning and reusability of nodes across different scenarios. For instance say you had a ROS node that took data from a USB camera and published it to a topic. You could use parameters to set the frame rate, USB port, and color depth. + +## ROS 2 Parameters CLI +The following command are help full when you are debugging or tracking down a problem. + +Command to list parameters +```bash +ros2 param list /node_name +``` + +Command to get parameter value +```bash +ros2 param get /node_name param_name +``` + +## Adding ROS 2 Parameters +Adding Parameters to a ROS 2 program is not that hard. Below you can see the steps. + +=== "Python" + ```python title="number_publisher.py" linenums="1" hl_lines="11 12 14 15" + #!/usr/bin/env python3 + import rclpy + from rclpy.node import Node + + from example_interfaces.msg import Int64 + + + class NumberPublisherNode(Node): + def __init__(self): + super().__init__("number_publisher") + self.declare_parameter("number_to_publish", 2) + self.declare_parameter("publish_frequency", 1.0) + + self.number_ = self.get_parameter("number_to_publish").value + self.publish_frequency_ = self.get_parameter("publish_frequency").value + + self.number_publisher_ = self.create_publisher(Int64, "number", 10) + self.number_timer_ = self.create_timer(1.0 / self.publish_frequency_, self.publish_number) + self.get_logger().info("Number publisher has been started.") + + def publish_number(self): + msg = Int64() + msg.data = self.number_ + self.number_publisher_.publish(msg) + + + def main(args=None): + rclpy.init(args=args) + node = NumberPublisherNode() + rclpy.spin(node) + rclpy.shutdown() + + + if __name__ == "__main__": + main() + ``` + + +=== "C++" + + ```cpp title="number_publisher.cpp" linenums="1" hl_lines="9 10 12 13" + #include "rclcpp/rclcpp.hpp" + #include "example_interfaces/msg/int64.hpp" + + class NumberPublisherNode : public rclcpp::Node + { + public: + NumberPublisherNode() : Node("number_publisher") + { + this->declare_parameter("number_to_publish", 2); + this->declare_parameter("publish_frequency", 1.0); + + number_ = this->get_parameter("number_to_publish").as_int(); + double publish_frequency = this->get_parameter("publish_frequency").as_double(); + + number_publisher_ = this->create_publisher("number", 10); + number_timer_ = this->create_wall_timer(std::chrono::milliseconds((int)(1000.0 / publish_frequency)), + std::bind(&NumberPublisherNode::publishNumber, this)); + RCLCPP_INFO(this->get_logger(), "Number publisher has been started."); + } + + private: + void publishNumber() + { + auto msg = example_interfaces::msg::Int64(); + msg.data = number_; + number_publisher_->publish(msg); + } + + int number_; + rclcpp::Publisher::SharedPtr number_publisher_; + rclcpp::TimerBase::SharedPtr number_timer_; + }; + + 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/docs/General ROS 2/Building ROS 2 Packages/Publisher.md b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Publisher.md similarity index 99% rename from docs/General ROS 2/Building ROS 2 Packages/Publisher.md rename to docs/General ROS 2/Building ROS 2 Packages/ROS 2 Publisher.md index 5d868f2..5c83d34 100755 --- a/docs/General ROS 2/Building ROS 2 Packages/Publisher.md +++ b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Publisher.md @@ -1,4 +1,4 @@ -# Publisher +# ROS 2 Publisher ## Sample Code Here is the code for creating a node that publishes strings to `/robot_news` at 2Hz. diff --git a/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Services.md b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Services.md new file mode 100755 index 0000000..a44ea30 --- /dev/null +++ b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Services.md @@ -0,0 +1,268 @@ +# ROS 2 Services + +## Remapping Service Topics +Remapping service names can come in handy when packages have duplicate service names or you want to run the same service twice. +```bash +ros2 run your_package your_node --ros-args -r old_name:=new_name +``` + +## Debugging a Service +Sometimes you do not know what type of message a service takes. To find the right type use the `ros2 service type` command: +```bash +ros2 service type /service_topic +``` +## Programming a Service +ROS 2, **services** enable synchronous communication between nodes. A service consists of a **request** and **response** model: one node (client) sends a request, and another node (service server) processes it and returns a response. + +Here is the code for making a simple ROS 2 Service + +=== "Python" + ```python title="add_two_ints_server.py" linenums="1" + #!/usr/bin/env python + import rclpy + from rclpy.node import Node + from example_interfaces.srv import AddTwoInts + + class AddTwoIntsServerNode(Node): + def __init__(self): + super().__init__("add_two_ints_server") + self.server_ = self.create_service(AddTwoInts, "add_two_ints", self.callback_add_two_ints); + self.get_logger().info("Add Two Ints Server has been started.") + + def callback_add_two_ints(self, request, response): + response.sum = request.a + request.b + self.get_logger().info(str(request.a) + " + " + str(request.b) + " = " + str(response.sum)) + return response + + + def main (args=None): + rclpy.init(args=args) + node = AddTwoIntsServerNode() + rclpy.spin(node) + rclpy.shutdown() + + if __name__ == '__main__': + main() + ``` +=== "C++" + ```cpp title="add_two_ints_server.cpp" linenums="1" + #include "rclcpp/rclcpp.hpp" + #include "example_interfaces/srv/add_two_ints.hpp" + + using std::placeholders::_1; + using std::placeholders::_2; + + class AddTwoIntServerNode : public rclcpp::Node + { + public: + AddTwoIntServerNode() : Node("add_two_ints_server") + { + server_ = this->create_service( + "add_two_ints", + std::bind(&AddTwoIntServerNode::callbackAddTwoInts, this, _1, _2)); + + RCLCPP_INFO(this->get_logger(), "Service server has been started."); + } + + private: + + void callbackAddTwoInts(const example_interfaces::srv::AddTwoInts::Request::SharedPtr request, + const example_interfaces::srv::AddTwoInts::Response::SharedPtr response) + { + response->sum = request->a + request->b; + RCLCPP_INFO(this->get_logger(), "%d + %d = %d", (int)request->a, (int)request->b, (int)response->sum); + } + + rclcpp::Service::SharedPtr server_; + }; + + int main(int argc, char **argv) + { + rclcpp::init(argc, argv); + auto node = std::make_shared(); + rclcpp::spin(node); + rclcpp::shutdown(); + return 0; + } + ``` + +## Programming a Client +ROS 2, **Clients** are nodes or components that initiate the service call, while the service itself is implemented by the server node. This interaction is commonly used for tasks requiring immediate feedback, such as retrieving or setting specific values. + +### Manual Making A Service Call +Sometime it is handy to make a ROS 2 service call manually. To do this use the `ros2 service call` command: +```bash +ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5, b: 1}" +``` + +### Without Object Orientated Programming +This program creates a client without using object orientated programming. +=== "Python" + ```python title="add_two_ints_client_no_oop.py" linenums="1" + #!/usr/bin/env python3 + import rclpy + from rclpy.node import Node + + from example_interfaces.srv import AddTwoInts + + def main(args=None): + rclpy.init(args=args) + node = Node("add_two_ints_client_no_oop") + + client = node.create_client(AddTwoInts, "add_two_ints") + while not client.wait_for_service(1.0): + node.get_logger().warn("Waiting for Server Add Two Ints...") + + request = AddTwoInts.Request() + request.a = 3 + request.b = 8 + + future = client.call_async(request) + rclpy.spin_until_future_complete(node, future) + + try: + response = future.result() + node.get_logger().info(str(request.a) + " + " + str(request.b) + " = " + str(response.sum)) + except Exception as e: + node.get_Togger().error("Service call failed %r" % (e,)) + + rclpy.shutdown() + + if __name__ == "__main__": + main() + ``` +=== "C++" + ```cpp title="add_two_ints_client_no_oop.cpp" linenums="1" + #include "rclcpp/rclcpp.hpp" + #include "example_interfaces/srv/add_two_ints.hpp" + + int main(int argc, char **argv) + { + rclcpp::init(argc, argv); + auto node = std::make_shared("add_two_ints_client_no_oop"); + + auto client = node->create_client("add_two_ints"); + + while(!client->wait_for_service(std::chrono::seconds(1))) + { + RCLCPP_WARN(node->get_logger(), "Waiting for the server to be up"); + } + + auto request = std::make_shared(); + request->a = 3; + request->b = 8; + + auto future = client->async_send_request(request); + + if(rclcpp::spin_until_future_complete(node, future) == rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_INFO(node->get_logger(), "%d + %d = %d", (int)request->a, (int)request->b, (int)future.get()->sum); + } + else{ + RCLCPP_ERROR(node->get_logger(), "Error while calling service"); + } + + rclcpp::shutdown(); + return 0; + } + ``` +### With Object Orientated Programming +This program creates a client with object orientated programming. +=== "Python" + ```python title="add_two_ints_client.py" linenums="1" + #!/usr/bin/env python3 + import rclpy + from rclpy.node import Node + from functools import partial + + from example_interfaces.srv import AddTwoInts + + + class AddTwoIntsClientNode(Node): + def __init__(self): + super().__init__("add_two_ints_client") + self.call_add_two_ints_server(6,7) + + def call_add_two_ints_server(self, a, b): + client = self.create_client(AddTwoInts, "add_two_ints") + while not client.wait_for_service(1.0): + self.get_logger().warn("Waiting For Server Add Ints...") + + request = AddTwoInts.Request() + request.a = a + request.b = b + + future = client.call_async(request) + + future.add_done_callback(partial(self.callback_call_add_two_ints, a=a, b=b)) + + def callback_call_add_two_ints(self, future, a, b): + try: + response = future.result() + self.get_logger().info(str(a) + " + " + str(b) + " = " + str(response.sum)) + except Exception as e: + self.get_Togger().error("Service call failed %r" % (e,)) + + + def main (args=None): + rclpy.init(args=args) + node = AddTwoIntsClientNode() + rclpy.spin(node) + rclpy.shutdown() + + if __name__ == '__main__': + main() + ``` +=== "C++" + ```cpp title="add_two_ints_client.cpp" linenums="1" + #include "rclcpp/rclcpp.hpp" + #include "example_interfaces/srv/add_two_ints.hpp" + + + class AddTwoIntsClientNode : public rclcpp::Node + { + public: + AddTwoIntsClientNode() : Node("add_two_ints_client") + { + thread1_ = std::thread(std::bind(&AddTwoIntsClientNode::callAddTwoIntsService, this, 10, 4)); + } + + void callAddTwoIntsService(int a, int b) + { + auto client = this->create_client("add_two_ints"); + while(!client->wait_for_service(std::chrono::seconds(1))) + { + RCLCPP_WARN(this->get_logger(), "Waiting for the server to be up"); + } + + auto request = std::make_shared(); + request->a = a; + request->b = b; + + auto future = client->async_send_request(request); + try + { + auto response = future.get(); + RCLCPP_INFO(this->get_logger(), "%d + %d = %d", a, b, (int)response->sum); + } + catch(const std::exception &e) + { + RCLCPP_ERROR(this->get_logger(), "Service call failed"); + } + + + } + + private: + std::thread thread1_; + }; + + 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/docs/General ROS 2/Building ROS 2 Packages/Subscriber.md b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Subscriber.md similarity index 98% rename from docs/General ROS 2/Building ROS 2 Packages/Subscriber.md rename to docs/General ROS 2/Building ROS 2 Packages/ROS 2 Subscriber.md index 965f67a..042a4c1 100755 --- a/docs/General ROS 2/Building ROS 2 Packages/Subscriber.md +++ b/docs/General ROS 2/Building ROS 2 Packages/ROS 2 Subscriber.md @@ -1,4 +1,4 @@ -# Subscriber +# ROS 2 Subscriber ## Sample Code Here is the code for creating a python node that subscribes to `/robot_news` at 2Hz. diff --git a/docs/General ROS 2/Micro ROS/Micro ROS Bridge.md b/docs/General ROS 2/Micro ROS/Micro ROS Bridge.md index 1082b97..867a443 100755 --- a/docs/General ROS 2/Micro ROS/Micro ROS Bridge.md +++ b/docs/General ROS 2/Micro ROS/Micro ROS Bridge.md @@ -6,7 +6,7 @@ docker run -it --rm -v /dev:/dev -v /dev/shm:/dev/shm --privileged --net=host microros/micro-ros-agent:$ROS_DISTRO serial --dev [YOUR BOARD PORT] -v6 ``` === "docker compose" - ```yaml title="docker-compose.yaml" + ```yaml title="docker-compose.yaml" linenums="1" services:  micro_ros_agent:    image: microros/micro-ros-agent:humble @@ -26,7 +26,7 @@ docker run -it --rm -v /dev:/dev -v /dev/shm:/dev/shm --privileged --net=host microros/micro-ros-agent:$ROS_DISTRO tcp4 --port 8888 -v6 ``` === "docker compose" - ```yaml title="docker-compose.yaml" + ```yaml title="docker-compose.yaml" linenums="1" services:  micro_ros_agent:    image: microros/micro-ros-agent:humble @@ -46,7 +46,7 @@ docker run -it --rm -v /dev:/dev -v /dev/shm:/dev/shm --privileged --net=host microros/micro-ros-agent:$ROS_DISTRO udp4 --port 8888 -v6 ``` === "docker compose" - ```yaml title="docker-compose.yaml" + ```yaml title="docker-compose.yaml" linenums="1" services:  micro_ros_agent:    image: microros/micro-ros-agent:humble diff --git a/docs/General ROS 2/ROS 2 Rosbag.md b/docs/General ROS 2/ROS 2 Rosbag.md new file mode 100755 index 0000000..7150a7f --- /dev/null +++ b/docs/General ROS 2/ROS 2 Rosbag.md @@ -0,0 +1,18 @@ +# ROS 2 Rosbag +## What is ROS 2 Rosbag +**Rosbag** is a tool for recording and replaying topic data. It is important because it enables debugging, analysis, and testing by capturing live data streams and replaying them for offline evaluation or simulation. + +### The command to record a topic +```bash +ros2 bag record /{topic to record} -o {filename} +``` + +### The command to play back a topic +```bash +ros2 bag play {filename} +``` + +### The command to get information about a recording +``` +ros2 bag info {filename} +``` \ No newline at end of file diff --git a/docs/General ROS 2/ROS 2 TF.md b/docs/General ROS 2/ROS 2 TF.md new file mode 100755 index 0000000..25d7d53 --- /dev/null +++ b/docs/General ROS 2/ROS 2 TF.md @@ -0,0 +1,37 @@ +# ROS 2 TF +## What is ROS 2 TF +**TF (Transform)** is a library used to keep track of coordinate frames and their relationships over time. It allows robots to understand where objects and parts of themselves are in 3D space, enabling tasks like localization, navigation, and manipulation. For example, it handles transformations between the robot base, sensors, and the world. + +## TF Tools +If you want to visualize the relation of the different TF's in a file use the following command to generate a PDF with a mermaid graph. +```bash +ros2 run tf2_tools view_frames +``` +```mermaid +graph TD; + base_link --> left_leg; + left_leg --> left_base; + left_base --> left_front_wheel; + left_base --> left_back_wheel; + + base_link --> right_leg; + right_leg --> right_base; + right_base --> right_front_wheel; + right_base --> right_back_wheel; + + base_link --> gripper_pole; + gripper_pole --> left_gripper; + left_gripper --> left_tip; + gripper_pole --> right_gripper; + right_gripper --> right_tip; + + base_link --> head; + head --> box; +``` + + +## Viewing TFs +To view a urdf file that has TFs in it use the following command. +```bash +ros2 launch urdf_tutorial display.launch.py model:=/opt/ros/humble/share/urdf_tutorial/urdf/08-macroed.urdf.xacro +``` diff --git a/docs/General ROS 2/URDF.md b/docs/General ROS 2/URDF.md new file mode 100755 index 0000000..c5557d7 --- /dev/null +++ b/docs/General ROS 2/URDF.md @@ -0,0 +1,195 @@ +# URDF +## What is a URDF +A **URDF (Unified Robot Description Format)** in ROS 2 is an XML file format used to describe a robot's structure. It defines the robot's physical properties, such as its links (rigid parts), joints (connections), dimensions, and more. URDFs are crucial for simulation, visualization, and robot control as they provide the framework for tools like RViz and Gazebo to understand the robot's design. + +here is a sample: +```xml title="my_robot.udrf" linenums="1" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` +### Materials +Materials allow color and other information to be added to a `` +```xml + + + +``` + + +### Links +**Links** are fundamental building blocks in a URDF file that represent the robot's physical parts. They allow you to create the individual parts of the robot and they are made up of multiple parts. +#### Visual +1. **`origin`**: + - Specifies the position and orientation of the visual geometry relative to the link's frame. + - Example: ``. +2. **`geometry`**: + - Describes the shape of the link. Shapes can be: + - **Box**: `` specifies dimensions. + - **Cylinder**: ``. + - **Sphere**: ``. + - **Mesh**: `` for complex shapes. +3. **`material`**: + - Specifies the color or texture of the link. + - **Color**: `` uses RGBA values (alpha for transparency). + - **Texture**: `` for applying an image. + +```xml + + + + + + + + + +``` + + +### Joints +Joints are what old different objects together. There are many different types of joints as shown below. There are also different parts to a joint. The `` link tells the join what it is attached to while the `` link tells it what its child is. The `` section tells the joint where it is located with the `xyz`being the coordinates and the `rpy` being role, pitch, and yaw. The `` section indicates a 1 for allowed moment and a 0 for no movement. Finally the `` section gives a lower and upper bound. +#### revolute +A hinge joint that rotates along the axis and has a limited range specified by the upper and lower limits that are in radians. +```xml + + + + + + + +``` +#### continuous +A continuous hinge joint that rotates around the axis and has no upper and lower limits. +```xml + + + + + + + +``` +#### prismatic +A sliding joint that slides along the axis, and has a limited range specified by the upper and lower limits in meters. +```xml + + + + + + + +``` +#### fixed +This is not really a joint because it cannot move. All degrees of freedom are locked. This type of joint does not require the ``, ``, ``, `` or ``. +```xml + + + + + +``` +#### floating +This joint allows motion for all 6 degrees of freedom. +```xml + + + + + +``` +#### planar +This joint allows motion in a plane perpendicular to the axis. +```xml + + + + + + + +``` + + +## Links For Help +Here are some links for help. +- [ROS.org](https://wiki.ros.org/urdf/XML) \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 8023f9b..a4ebc47 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,12 +1,12 @@ # Welcome Welcome this is the documenation for the Rhodes State ROS 2 Robotics equitment. This information is a collection of useful tips gathered from [Clearpath Robot Docs](https://docs.clearpathrobotics.com/docs/robots/), [Clearpath ROS 2](https://docs.clearpathrobotics.com/docs/ros/), and [Turtlebot 4 Manuel](https://turtlebot.github.io/turtlebot4-user-manual/). It also has informtation from the following courses; [ROS2 For Beginners](https://www.udemy.com/course/ros2-for-beginners) +# General ROS 2 +This section covers general topics and tools that are useful for ROS 2 development. -## General ROS 2 -This section covers genral topics and tools that are useful for ROS 2 development. +# Husky 200 +This section contains all the important information on the Husky 200, including how to program it and use ROS 2. -## Husky 200 -This section has all the importent information on the Husky 200 and how to program it and using ROS 2. +# Turtlebot 4 +This section contains information on the Turtlebot 4, including how to program it and use ROS 2. -## Turtlebot 4 -This section has information on the Turtlebot 4 and how to program it and using ROS 2. \ No newline at end of file