From 3121c77eabf714c7aa3de8a644f8b1f05004c835 Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Wed, 4 Aug 2021 15:07:50 +0200 Subject: [PATCH 01/14] First commit --- _data/docs.yml | 11 +- .../doc/highPriorityPath.png | Bin 46593 -> 0 bytes .../doc/scheduling_01.png | Bin 6202 -> 0 bytes .../doc/scheduling_02.png | Bin 7693 -> 0 bytes .../doc/scheduling_LET.png | Bin 42663 -> 0 bytes .../doc/sensePlanActScheme.png | Bin 14245 -> 0 bytes .../doc/sensorFusion_01.png | Bin 8244 -> 0 bytes .../doc/sensorFusion_02.png | Bin 23883 -> 0 bytes .../doc/sensorFusion_03.png | Bin 22839 -> 0 bytes .../core/programming_rcl_rclc/index.md | 1055 ----------------- .../micro-ROS/micro-ROS.md | 46 + .../programming_rcl_rclc/node/node.md | 70 ++ .../programming_rcl_rclc/overview/index.md | 17 + .../parameters/parameters.md | 124 ++ .../programming_rcl_rclc/pub_sub/pub_sub.md | 240 ++++ .../tutorials/programming_rcl_rclc/qos/QoS.md | 45 + .../programming_rcl_rclc/service/services.md | 254 ++++ _includes/docs_nav.html | 3 + 18 files changed, 809 insertions(+), 1056 deletions(-) delete mode 100644 _docs/tutorials/core/programming_rcl_rclc/doc/highPriorityPath.png delete mode 100644 _docs/tutorials/core/programming_rcl_rclc/doc/scheduling_01.png delete mode 100644 _docs/tutorials/core/programming_rcl_rclc/doc/scheduling_02.png delete mode 100644 _docs/tutorials/core/programming_rcl_rclc/doc/scheduling_LET.png delete mode 100644 _docs/tutorials/core/programming_rcl_rclc/doc/sensePlanActScheme.png delete mode 100644 _docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_01.png delete mode 100644 _docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_02.png delete mode 100644 _docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_03.png delete mode 100644 _docs/tutorials/core/programming_rcl_rclc/index.md create mode 100644 _docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md create mode 100644 _docs/tutorials/programming_rcl_rclc/node/node.md create mode 100644 _docs/tutorials/programming_rcl_rclc/overview/index.md create mode 100644 _docs/tutorials/programming_rcl_rclc/parameters/parameters.md create mode 100644 _docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md create mode 100644 _docs/tutorials/programming_rcl_rclc/qos/QoS.md create mode 100644 _docs/tutorials/programming_rcl_rclc/service/services.md diff --git a/_data/docs.yml b/_data/docs.yml index 19bad0a5..07fbe17c 100644 --- a/_data/docs.yml +++ b/_data/docs.yml @@ -50,7 +50,6 @@ - tutorials/core/overview - tutorials/core/first_application_linux - tutorials/core/first_application_rtos - - tutorials/core/programming_rcl_rclc - tutorials/core/zephyr_emulator - tutorials/core/teensy_with_arduino @@ -66,6 +65,16 @@ - tutorials/advanced/benchmarking - tutorials/advanced/tracing +- title: Programming with rcl and rclc + docs: + - tutorials/programming_rcl_rclc/overview + - tutorials/programming_rcl_rclc/node + - tutorials/programming_rcl_rclc/pub_sub + - tutorials/programming_rcl_rclc/service + - tutorials/programming_rcl_rclc/parameters + - tutorials/programming_rcl_rclc/qos + - tutorials/programming_rcl_rclc/micro-ROS + - title: Unmaintained Tutorials docs: - tutorials/old/microros_nuttx_bsp diff --git a/_docs/tutorials/core/programming_rcl_rclc/doc/highPriorityPath.png b/_docs/tutorials/core/programming_rcl_rclc/doc/highPriorityPath.png deleted file mode 100644 index 664c4e113b63c0edbfac69c549ca98ef4017a43c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46593 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 NbxqSiqd*N8_8-C8h2Q`H diff --git a/_docs/tutorials/core/programming_rcl_rclc/doc/scheduling_02.png b/_docs/tutorials/core/programming_rcl_rclc/doc/scheduling_02.png deleted file mode 100644 index c30783a78fd098521c72f78e7bcf6926fb0bde4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7693 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 Vca+o#@J}cjWU5}UN86h%>6>=~;h_O99^_Dby? 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 Aod5s; diff --git a/_docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_03.png b/_docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_03.png deleted file mode 100644 index d0615c1b302c4d3359a60ef498ccdb7b082b7276..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22839 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 Orh#388@(}fi}@e=LGtkc diff --git a/_docs/tutorials/core/programming_rcl_rclc/index.md b/_docs/tutorials/core/programming_rcl_rclc/index.md deleted file mode 100644 index 8d5ff504..00000000 --- a/_docs/tutorials/core/programming_rcl_rclc/index.md +++ /dev/null @@ -1,1055 +0,0 @@ ---- -title: Programming with rcl and rclc -permalink: /docs/tutorials/core/programming_rcl_rclc/ ---- - - - -In this tutorial, you'll learn the basics of the micro-ROS C API. The major concepts (publishers, subscriptions, services,timers, ...) are identical with ROS 2. They even rely on the *same* implementation, as the micro-ROS C API is based on the ROS 2 client support library (rcl), enriched with a set of convenience functions by the package [rclc](https://github.com/ros2/rclc/). That is, rclc does not add a new layer of types on top of rcl (like rclcpp and rclpy do) but only provides functions that ease the programming with the rcl types. New types are introduced only for concepts that are missing in rcl, such as the concept of an executor. - -* [Creating a node](#node) -* [Publishers and subscriptions](#pub_sub) -* [Services](#services) -* [Timers](#timers) -* [Lifecycle](#lifecycle) -* [Rclc Executor](#rclc_executor) - -## Creating a Node - -To simplify the creation of a node with rcl, rclc provides two functions `rclc_support_init(..)` and `rclc_node_init_default(..)` in [rclc/init.h](https://github.com/ros2/rclc/blob/master/rclc/include/rclc/init.h) and [rclc/node.h](https://github.com/ros2/rclc/blob/master/rclc/include/rclc/node.h), respectively. The first lines of the main function of a micro-ROS programm are: - -```C -rcl_allocator_t allocator = rcl_get_default_allocator(); -rclc_support_t support; -rcl_ret_t rc; - -rc = rclc_support_init(&support, argc, argv, &allocator); -if (rc != RCL_RET_OK) { - ... // Some error reporting. - return -1; -} - -rcl_node_t my_node; -rc = rclc_node_init_default(&my_node, "my_node_name", "my_namespace", &support); -if (rc != RCL_RET_OK) { - ... // Some error reporting. - return -1; -} -``` - -## Publishers and Subscriptions - -Publishers and subscribers are most easily created with the rclc package. - -Creating a publisher by `rclc_publisher_init_default(..)` from [rclc/publisher.h](https://github.com/ros2/rclc/blob/master/rclc/include/rclc/publisher.h): - -```C -rcl_publisher_t my_pub; -std_msgs__msg__String my_msg; -const char * my_topic = "topic_0"; -const rosidl_message_type_support_t * my_type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String); - -rc = rclc_publisher_init_default(&my_pub, &my_node, &my_type_support, &my_topic_name); -if (RCL_RET_OK != rc) { - printf("Error in rclc_publisher_init_default.\n"); - return -1; -} -``` - -Initializing a message: - -```C -std_msgs__msg__String__init(&pub_msg); -const unsigned int PUB_MSG_SIZE = 20; -char pub_string[PUB_MSG_SIZE]; -snprintf(pub_string, 13, "%s", "Hello World!"); -rosidl_generator_c__String__assignn(&pub_msg, pub_string, PUB_MSG_SIZE); -``` - -Creating a subscription by `rclc_subscription_init_default(..)` from [rclc/subscription.h](https://github.com/ros2/rclc/blob/master/rclc/include/rclc/subscription.h): - -```C -rcl_subscription_t my_sub = rcl_get_zero_initialized_subscription(); -rc = rclc_subscription_init_default(&my_sub, &my_node, &my_type_support, &my_topic_name); -if (rc != RCL_RET_OK) { - printf("Failed to create subscriber.\n"); - return -1; -} -``` - -## Services - -ROS 2 services is another communication mechanism between nodes. Services implement a client-server paradigm based on ROS 2 messages and types. Further information about ROS 2 services can be found [here](https://index.ros.org/doc/ros2/Tutorials/Services/Understanding-ROS2-Services/) - -Ready to use code related to this tutorial can be found in [`micro-ROS-demos/rclc/addtwoints_server`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_server/main.c) and [`micro-ROS-demos/rclc/addtwoints_client`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_client/main.c) folders. - -Note: Services are not supported in rclc package yet. Therefore, for the moment, the configuration is described using the RCL layer. However, we are working to port them to the RCLC soon. - -Starting from a code where RCL is initialized and a micro-ROS node is created, these steps are required in order to generate a service server: - -```C -// Creating service server and options -rcl_service_options_t service_options = rcl_service_get_default_options(); -rcl_service_t server = rcl_get_zero_initialized_service(); - -// Initializing service server -rcl_service_init(&server, &node, ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts), "addtwoints", &service_options); - -// Init service server wait set -rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set(); -rcl_wait_set_init(&wait_set, 0, 0, 0, 0, 1, 0, &context, rcl_get_default_allocator()); - -``` - -On the other hand the service client initialization looks like that: - -```C -// Creating service client and options -rcl_client_options_t client_options = rcl_client_get_default_options(); -rcl_client_t client = rcl_get_zero_initialized_client(); - -// Initializing service client -rcl_client_init(&client, &node, ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts), "addtwoints", &client_options) - -// Init service client wait set -rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set(); -rcl_wait_set_init(&wait_set, 0, 0, 0, 1, 0, 0, &context, rcl_get_default_allocator()); -``` - -First of all, by looking at `AddTwoInts.srv` type definition it is possible to determine request and reply elements of the service. Service client will make a request with two integers and service server should send its sum as a response. - -```C -int64 a -int64 b ---- -int64 sum -``` - -Once service client and server are configured, service client can perform a request and wait for reply: - -```C -// Creating a service request -int64_t seq; -example_interfaces__srv__AddTwoInts_Request req; -req.a = 24; -req.b = 42; - -// Sending the request -rcl_send_request(&client, &req, &seq) -printf("Send service request %d + %d. Seq %ld\n",(int)req.a, (int)req.b, (int)seq); - -// Wait for response -bool done = false; -do { - rcl_wait_set_clear(&wait_set); - - size_t index; - rcl_wait_set_add_client(&wait_set, &client, &index); - - rcl_wait(&wait_set, RCL_MS_TO_NS(1)); - - // If wait set client element is not null, response is ready - if (wait_set.clients[index]) { - rmw_request_id_t req_id; - - // Create a service response struct - example_interfaces__srv__AddTwoInts_Response res; - - // Take the response - rcl_ret_t rc = rcl_take_response(&client, &req_id, &res); - - if (RCL_RET_OK == rc) { - printf("Received service response %d + %d = %d. Seq %d\n",(int)req.a, (int)req.b, (int)res.sum,req_id.sequence_number); - done = true; - } - } -} while ( !done ); -``` - -On service server side, the ROS 2 node should be waiting for service requests and generate service replies: - -```C -while(1){ - rcl_wait_set_clear(&wait_set); - - size_t index; - rcl_wait_set_add_service(&wait_set, &service, &index); - - rcl_wait(&wait_set, RCL_MS_TO_NS(1)); - - // If wait set service element is not null, request is ready - if (wait_set.services[index]) { - rmw_request_id_t req_id; - - // Create a service request struct - example_interfaces__srv__AddTwoInts_Request req; - - // Take the request - rcl_take_request(&service, &req_id, &req); - - printf("Service request value: %d + %d. Seq %d\n", (int)req.a, (int)req.b, (int)req_id.sequence_number); - - // Create a service response, fill the result and send it - example_interfaces__srv__AddTwoInts_Response res; - res.sum = req.a + req.b; - rcl_send_response(&service, &req_id,&res); - } -} -``` - -## Timers - -A timer can be created with the rclc-package with the function -`rclc_timer_init_default(..)` in [rclc/timer.h](https://github.com/ros2/rclc/blob/master/rclc/include/rclc/timer.h): - -```C -// create a timer, which will call the publisher with period=`timer_timeout` ms in the 'my_timer_callback' -rcl_timer_t my_timer; -const unsigned int timer_timeout = 1000; -rc = rclc_timer_init_default(&my_timer, &support, RCL_MS_TO_NS(timer_timeout), my_timer_callback); -if (rc != RCL_RET_OK) { - printf("Error in rcl_timer_init_default.\n"); - return -1; -} else { - printf("Created timer with timeout %d ms.\n", timer_timeout); -} -``` - -## Lifecycle - -The rclc lifecycle package provides convenience functions in C to bundle an rcl node with the ROS 2 Node Lifecycle state machine, similar to the [rclcpp Lifecycle Node](https://github.com/ros2/rclcpp/blob/master/rclcpp_lifecycle/include/rclcpp_lifecycle/lifecycle_node.hpp) for C++. - -This tutorial show-cases how to set up an rclc lifecycle node, transition through its lifecycle states, and assign callbacks to lifecycle transitions. - -### Initialization - -Creation of a lifecycle node as a bundle of an rcl node and the rcl lifecycle state machine. - -```C -#include "rclc_lifecycle/rclc_lifecycle.h" - -rcl_allocator_t allocator = rcl_get_default_allocator(); -rclc_support_t support; -rcl_ret_t rc; - -// create rcl node -rc = rclc_support_init(&support, argc, argv, &allocator); -rcl_node_t my_node; -rc = rclc_node_init_default(&my_node, "my_lifecycle_node", "rclc", &support); - -// rcl state machine -rcl_lifecycle_state_machine_t state_machine = - rcl_lifecycle_get_zero_initialized_state_machine(); -... - -// create the lifecycle node -rclc_lifecycle_node_t my_lifecycle_node; -rcl_ret_t rc = rclc_make_node_a_lifecycle_node( - &my_lifecycle_node, - &my_node, - &state_machine, - &allocator); -``` - -Optionally create hooks for lifecycle state changes. - -```C -// declare callback -rcl_ret_t my_on_configure() { - printf(" >>> my_lifecycle_node: on_configure() callback called.\n"); - return RCL_RET_OK; -} -... - -// register callbacks -rclc_lifecycle_register_on_configure(&my_lifecycle_node, &my_on_configure); -``` - -### Running - -Change states of the lifecycle node, e.g. - -```C -bool publish_transition = true; -rc += rclc_lifecycle_change_state( - &my_lifecycle_node, - lifecycle_msgs__msg__Transition__TRANSITION_CONFIGURE, - publish_transition); -rc += rclc_lifecycle_change_state( - &my_lifecycle_node, - lifecycle_msgs__msg__Transition__TRANSITION_ACTIVATE, - publish_transition); -... -``` - -Except for error processing transitions, transitions are usually triggered from outside, e.g., by ROS 2 services. - -### Cleaning Up - -To clean everything up, simply do - -```C -rc += rcl_lifecycle_node_fini(&my_lifecycle_node, &allocator); -``` - -### Example and Limitations - -An example of the rclc Lifecycle Node is given in the file `lifecycle_node.c` in the [rclc_examples](https://github.com/ros2/rclc/tree/master/rclc_examples) package. - -The state machine publishes state changes, however, lifecycle services are not yet exposed via ROS 2 services ([ros2/rclc#40](https://github.com/ros2/rclc/issues/40)). - -## rclc Executor - -The rclc Executor provides a C API to manage the execution of subscription and timer callbacks, similar to the [rclcpp Executor](https://github.com/ros2/rclcpp/blob/master/rclcpp/include/rclcpp/executor.hpp) for C++. The rclc Executor is optimized for resource-constrained devices and provides additional features that allow the manual implementation of deterministic schedules with bounded end-to-end latencies. - -In this tutorial we provide two examples: - -* Example 1: Hello-World example consisting of one executor and one publisher, timer and subscription. -* Example 2: Triggered execution example, demonstrating the capability of synchronizing the execution of callbacks based on the availability of new messages - -Further examples for using the rclc Executor in mobile robotics scenarios and real-time embedded applications can be found in the [rclc](https://github.com/ros2/rclc/tree/master/rclc) repository. - -### Example 1: 'Hello World' - -To start with, we provide a very simple example for an rclc Executor with one timer and one subscription, so to say, a 'Hello world' example. It consists of a publisher, sending a 'hello world' message to a subscriber, which then prints out the received message on the console. - -First, you include some header files, in particular the [rclc/rclc.h](https://github.com/ros2/rclc/blob/master/rclc/include/rclc/rclc.h) and [rclc/executor.h](https://github.com/ros2/rclc/blob/master/rclc/include/rclc/executor.h). - -```C -#include -#include -#include -#include -``` - -We define a publisher and two strings, which will be used later. - -```C -rcl_publisher_t my_pub; -std_msgs__msg__String pub_msg; -std_msgs__msg__String sub_msg; -``` - -The subscription callback casts the message parameter `msgin` to an equivalent type of `std_msgs::msg::String` in C and prints out the received message. - -```C -void my_subscriber_callback(const void * msgin) -{ - const std_msgs__msg__String * msg = (const std_msgs__msg__String *)msgin; - if (msg == NULL) { - printf("Callback: msg NULL\n"); - } else { - printf("Callback: I heard: %s\n", msg->data.data); - } -} -``` - -The timer callback publishes the message `pub_msg` with the publisher `my_pub`, which is initialized later in `main()`. - -```C -void my_timer_callback(rcl_timer_t * timer, int64_t last_call_time) -{ - rcl_ret_t rc; - UNUSED(last_call_time); - if (timer != NULL) { - rc = rcl_publish(&my_pub, &pub_msg, NULL); - if (rc == RCL_RET_OK) { - printf("Published message %s\n", pub_msg.data.data); - } else { - printf("Error in timer_callback: Message %s could not be published\n", pub_msg.data.data); - } - } else { - printf("Error in timer_callback: timer parameter is NULL\n"); - } -} -``` - -After defining the callback functions, we present now the `main()` function. First, some initialization is necessary to create later rcl objects. That is an `allocator` for dynamic memory allocation, and a `support` object, which contains some rcl-objects simplifying the initialization of an rcl-node, an rcl-subscription, an rcl-timer and an rclc-executor. - -```C -int main(int argc, const char * argv[]) -{ - rcl_allocator_t allocator = rcl_get_default_allocator(); - rclc_support_t support; - rcl_ret_t rc; - - // create init_options - rc = rclc_support_init(&support, argc, argv, &allocator); - if (rc != RCL_RET_OK) { - printf("Error rclc_support_init.\n"); - return -1; - } -``` - -Next, you define a ROS 2 node `my_node` and initialize it with `rclc_executor_init_default()`: - -```C - // create rcl_node - rcl_node_t my_node; - rc = rclc_node_init_default(&my_node, "node_0", "executor_examples", &support); - if (rc != RCL_RET_OK) { - printf("Error in rclc_node_init_default\n"); - return -1; - } -``` - -You can create a publisher to publish topic 'topic_0' with type std_msg::msg::String with the following code: - -```C -const char * topic_name = "topic_0"; -const rosidl_message_type_support_t * my_type_support = - ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String); - -rc = rclc_publisher_init_default(&my_pub, &my_node, my_type_support, topic_name); -if (RCL_RET_OK != rc) { - printf("Error in rclc_publisher_init_default %s.\n", topic_name); - return -1; -} -``` - -Note, that variable `my_pub` was defined globally, so it can be used by the timer callback. - -You can create a timer `my_timer` with a period of one second, which executes the callback `my_timer_callback` like this: - -```C - rcl_timer_t my_timer; - const unsigned int timer_timeout = 1000; // in ms - rc = rclc_timer_init_default(&my_timer, &support, RCL_MS_TO_NS(timer_timeout), my_timer_callback); - if (rc != RCL_RET_OK) { - printf("Error in rcl_timer_init_default.\n"); - return -1; - } else { - printf("Created timer with timeout %d ms.\n", timer_timeout); - } -``` - -The string `Hello World!` can be assigned directly to the message of the publisher `pub_msg.data`. First the publisher message is initialized with `std_msgs__msg__String__init`. Then you need to allocate memory for `pub_msg.data.data`, set the maximum capacity `pub_msg.data.capacity` and set the length of the message `pub_msg.data.size` accordingly. You can assign the content of the message with `snprintf` of `pub_msg.data.data`. - -```C - // assign message to publisher - std_msgs__msg__String__init(&pub_msg); - const unsigned int PUB_MSG_CAPACITY = 20; - pub_msg.data.data = malloc(PUB_MSG_CAPACITY); - pub_msg.data.capacity = PUB_MSG_CAPACITY; - snprintf(pub_msg.data.data, pub_msg.data.capacity, "Hello World!"); - pub_msg.data.size = strlen(pub_msg.data.data); -``` - -A subscription `my_sub`can be defined like this: - -```C - rcl_subscription_t my_sub = rcl_get_zero_initialized_subscription(); - rc = rclc_subscription_init_default(&my_sub, &my_node, my_type_support, topic_name); - if (rc != RCL_RET_OK) { - printf("Failed to create subscriber %s.\n", topic_name); - return -1; - } else { - printf("Created subscriber %s:\n", topic_name); - } -``` - -The global message for this subscription `sub_msg` needs to be initialized with: - -```C - std_msgs__msg__String__init(&sub_msg); -``` - -Now, all preliminary steps are done, and you can define and initialized the rclc executor with: - -```C - rclc_executor_t executor; - executor = rclc_executor_get_zero_initialized_executor(); -``` - -In the next step, executor is initialized with the ROS 2 `context`, the number of communication objects `num_handles` and an `allocator`. The number of communication objects defines the total number of timers and subscriptions, the executor shall manage. In this example, the executor will be setup with one timer and one subscription. - -```C - // total number of handles = #subscriptions + #timers - unsigned int num_handles = 1 + 1; - rclc_executor_init(&executor, &support.context, num_handles, &allocator); -``` - -Now, you can add a subscription with the function `rclc_c_executor_add_subscription` with the previously defined subscription `my_sub`, its message `sub_msg`and its callback `my_subscriber_callback`: - -```C -rc = rclc_executor_add_subscription(&executor, &my_sub, &sub_msg, &my_subscriber_callback, ON_NEW_DATA); -if (rc != RCL_RET_OK) { - printf("Error in rclc_executor_add_subscription. \n"); -} -``` - -The option `ON_NEW_DATA` selects the execution semantics of the spin-method. In this example, the callback of the subscription `my_sub`is only called if new data is available. - -Note: Another execution semantics is `ALWAYS`, which means, that the subscription callback is always executed when the spin-method of the executor is called. This option might be useful in cases in which the callback shall be executed at a fixed rate irrespective of new data is available or not. If you choose this option, then the callback will be executed with message argument `NULL` if no new data is available. Therefore you need to make sure, that your callback also accepts `NULL` as message argument. - -Likewise, you can add the timer `my_timer` with the function `rclc_c_executor_add_timer`: - -```C -rclc_executor_add_timer(&executor, &my_timer); -if (rc != RCL_RET_OK) { - printf("Error in rclc_executor_add_timer.\n"); -} -``` - -A key feature of the rclc Executor is that the order of these `rclc-executor-add-*`-functions matters. The order in which these functions are called defines the static processing order of the callbacks when the spin-function of the executor is running. - -In this example, the timer was added to the executor before the subscription. Therefore, if the timer is ready and also a new message for the subscription is available, then the timer is executed first and after it the subscription. Such a behavior cannot be defined currently with the rclcpp Executor and is useful to implement a deterministic execution semantics. - -Finally, you can run the executor with `rclc_executor_spin()`: - -```C - rclc_executor_spin(&executor); -``` - -This function runs forever without coming back. In this example, however, we want to publish the message only ten times. Therefore we are using the spin-method `rclc_executor_spin_some`, which spins only once and returns. The wait timeout for checking for new messages at the DDS-queue or waiting timers to get ready is configured to be one second. - -```C -for (unsigned int i = 0; i < 10; i++) { - // timeout specified in nanoseconds (here 1s) - rclc_executor_spin_some(&executor, 1000 * (1000 * 1000)); -} -``` - -At the end, you need to free dynamically allocated memory: - -```C - // clean up - rc = rclc_executor_fini(&executor); - rc += rcl_publisher_fini(&my_pub, &my_node); - rc += rcl_timer_fini(&my_timer); - rc += rcl_subscription_fini(&my_sub, &my_node); - rc += rcl_node_fini(&my_node); - rc += rclc_support_fini(&support); - std_msgs__msg__String__fini(&pub_msg); - std_msgs__msg__String__fini(&sub_msg); - - if (rc != RCL_RET_OK) { - printf("Error while cleaning up!\n"); - return -1; - } -return 0; -} // main -``` - -This completes the example. The source code can be found in the package rclc-examples [rclc-examples/example_executor_convenience.c](https://github.com/ros2/rclc/blob/master/rclc_examples/src/example_executor_convenience.c). - -#### Example 2: Triggered execution - -In robotic applications often multiple sensors are used to improve localization precision. These sensors can have different frequencies, for example, a high frequency IMU sensor and a low frequency laser scanner. One way is to trigger execution upon arrival of a laser scan and only then evaluate the most recent data from the aggregated IMU data. - -This example demonstrates the additional feature of the rclc executor to trigger the execution of callbacks based on the availability of input data. - -We setup one executor with two publishers, one with 100ms and one with 1000ms period. Then we setup one executor for two subscriptions. Their callbacks shall both be executed if the message of the publisher with the lower frequency arrives. - -The output of this code example will look like this: - -```C -Created timer 'my_string_timer' with timeout 100 ms. -Created 'my_int_timer' with timeout 1000 ms. -Created subscriber topic_0: -Created subscriber topic_1: -Executor_pub: number of DDS handles: 2 -Executor_sub: number of DDS handles: 2 -Published: Hello World! 0 -Published: Hello World! 1 -Published: Hello World! 2 -Published: Hello World! 3 -Published: Hello World! 4 -Published: Hello World! 5 -Published: Hello World! 6 -Published: Hello World! 7 -Published: Hello World! 8 -Published: Hello World! 9 -Published: 0 -Callback 1: Hello World! 9 <--- -Callback 2: 0 <--- -Published: Hello World! 10 -Published: Hello World! 11 -Published: Hello World! 12 -Published: Hello World! 13 -Published: Hello World! 14 -Published: Hello World! 15 -Published: Hello World! 16 -Published: Hello World! 17 -Published: Hello World! 18 -Published: Hello World! 19 -Published: 1 -Callback 1: Hello World! 19 <--- -Callback 2: 1 <--- -``` - -This output shows, that the callbacks are executed, only if both message have received new data. In that case, the latest data of high-frequency topic is used. - -You learn in this tutorial - -* how to use pre-defined trigger conditions -* how to write custom-defined trigger conditions -* how to run multiple executors -* how to setup quality-of-service parameters for a subscription - -We start with the necessary includes for string and int messages, `` and `std_msgs/msg/int32.h` respectivly. Then the necessary includes follow for the rclc convenience functions `rclc.h` and the the rclc executor `executor.h`: - -```C -#include -#include -#include -#include - -#include -#include -``` - -Then, global variables for the publishers and subscriptions as well as their messages are defined, which are initialized in the `main()` function and used in the corresponding callbacks: - -```C -rcl_publisher_t my_pub; -rcl_publisher_t my_int_pub; -std_msgs__msg__String sub_msg; -std_msgs__msg__Int32 pub_int_msg; -int pub_int_value; -std_msgs__msg__Int32 sub_int_msg; -int pub_string_value; -``` - -For the custom-defined trigger conditions, the type `pub_trigger_object_t` and the type `sub_trigger_object_t` are defined. - -```C -typedef struct -{ - rcl_timer_t * timer1; - rcl_timer_t * timer2; -} pub_trigger_object_t; - -typedef struct -{ - rcl_subscription_t * sub1; - rcl_subscription_t * sub2; -} sub_trigger_object_t; -``` - -The executor for the publishers shall publish when any of corresponding timers for the publishers is ready. That is the or-logic. You could also use the predefined `rclc_executor_trigger_any` trigger condition, but this example shows how you can write your own trigger conditions. - -In principle, the condition gets a list of handles, the length of this list, and the pre-defined condition type. In this case, we expect `pub_trigger_object_t`. First, the parameter `obj` is cased to this type (`comm_obj`). Then, each element of the handle list is checked for new data (or a timer is ready) by evaluating the field `handles[i].data_available` and its handle pointer is compared to the pointer of the communicatoin object. If at least one timer is ready, then the trigger condition returns true. - -```C -bool pub_trigger(rclc_executor_handle_t * handles, unsigned int size, void * obj) -{ - if (handles == NULL) { - printf("Error in pub_trigger: 'handles' is a NULL pointer\n"); - return false; - } - if (obj == NULL) { - printf("Error in pub_trigger: 'obj' is a NULL pointer\n"); - return false; - } - pub_trigger_object_t * comm_obj = (pub_trigger_object_t *) obj; - bool timer1 = false; - bool timer2 = false; - //printf("pub_trigger ready set: "); - for (unsigned int i = 0; i < size; i++) { - if (handles[i].data_available) { - void * handle_ptr = rclc_executor_handle_get_ptr(&handles[i]); - if (handle_ptr == comm_obj->timer1) { - timer1 = true; - } - if (handle_ptr == comm_obj->timer2) { - timer2 = true; - } - } - } - return (timer1 || timer2); -} -``` - -The trigger condition for the subscription `sub_trigger`shall implement an AND-logic. That is, only if both subscriptions have received a new message, then the executor shall start processing the callbacks. - -The implementation is analogous to `pub_trigger`. The only difference is, that this trigger returns true, if both subscriptions have been found in the handle list. This is implemented in the condition `sub1 && sub2` of the last if-statement. - -```C -bool sub_trigger(rclc_executor_handle_t * handles, unsigned int size, void * obj) -{ - if (handles == NULL) { - printf("Error in sub_trigger: 'handles' is a NULL pointer\n"); - return false; - } - if (obj == NULL) { - printf("Error in sub_trigger: 'obj' is a NULL pointer\n"); - return false; - } - sub_trigger_object_t * comm_obj = (sub_trigger_object_t *) obj; - bool sub1 = false; - bool sub2 = false; - //printf("sub_trigger ready set: "); - for (unsigned int i = 0; i < size; i++) { - if (handles[i].data_available == true) { - void * handle_ptr = rclc_executor_handle_get_ptr(&handles[i]); - - if (handle_ptr == comm_obj->sub1) { - sub1 = true; - } - if (handle_ptr == comm_obj->sub2) { - sub2 = true; - } - } - } - return (sub1 && sub2); -} -``` - -Like in the Hello-World example, the subscription callbacks just prints out the received message. - -The `my_string_subscriber` callback prints out the string of the message `msg->data.data`: - -```C -void my_string_subscriber_callback(const void * msgin) -{ - const std_msgs__msg__String * msg = (const std_msgs__msg__String *)msgin; - if (msg == NULL) { - printf("my_string_subscriber_callback: msgin is NULL\n"); - } else { - printf("Callback 1: %s\n", msg->data.data); - } -} -``` - -The integer callback prints out the received integer `msg->data`: - -```C -void my_int_subscriber_callback(const void * msgin) -{ - const std_msgs__msg__Int32 * msg = (const std_msgs__msg__Int32 *)msgin; - if (msg == NULL) { - printf("my_int_subscriber_callback: msgin is NULL\n"); - } else { - printf("Callback 2: %d\n", msg->data); - } -} -``` - -To publish messages with different frequencies, we setup two timers. -One timer to publish a string message, the `my_timer_string_callback` and one timer to publish the integer, the `my_timer_int_callback`. - -In the `my_timer_string_callback`, the message `pub_msg` is created and filled with the string `Hello World` plus an integer, which is incremented by one, each time the timer callback is called. The the message is published with `rcl_publish()` - -The macro `UNUSED` is a workaround for the linter warning, that the second parameter `last_call_time` is not used. - -```C -#define UNUSED(x) (void)x; - -void my_timer_string_callback(rcl_timer_t * timer, int64_t last_call_time) -{ - rcl_ret_t rc; - UNUSED(last_call_time); - if (timer != NULL) { - //printf("Timer: time since last call %d\n", (int) last_call_time); - - std_msgs__msg__String pub_msg; - std_msgs__msg__String__init(&pub_msg); - const unsigned int PUB_MSG_CAPACITY = 20; - pub_msg.data.data = malloc(PUB_MSG_CAPACITY); - pub_msg.data.capacity = PUB_MSG_CAPACITY; - snprintf(pub_msg.data.data, pub_msg.data.capacity, "Hello World!%d", pub_string_value++); - pub_msg.data.size = strlen(pub_msg.data.data); - - rc = rcl_publish(&my_pub, &pub_msg, NULL); - if (rc == RCL_RET_OK) { - printf("Published: %s\n", pub_msg.data.data); - } else { - printf("Error in my_timer_string_callback: publishing message %s\n", pub_msg.data.data); - } - std_msgs__msg__String__fini(&pub_msg); - } else { - printf("Error in my_timer_string_callback: timer parameter is NULL\n"); - } -} -``` - -Likewise, the `my_timer_int_callback` increments the integer value `pub_int_value` in every call and assigns it to the message field `pub_int_msg.data`. Then the message is published with `rcl_publish()` - -```C -void my_timer_int_callback(rcl_timer_t * timer, int64_t last_call_time) -{ - rcl_ret_t rc; - UNUSED(last_call_time); - if (timer != NULL) { - //printf("Timer: time since last call %d\n", (int) last_call_time); - pub_int_msg.data = pub_int_value++; - rc = rcl_publish(&my_int_pub, &pub_int_msg, NULL); - if (rc == RCL_RET_OK) { - printf("Published: %d\n", pub_int_msg.data); - } else { - printf("Error in my_timer_int_callback: publishing message %d\n", pub_int_msg.data); - } - } else { - printf("Error in my_timer_int_callback: timer parameter is NULL\n"); - } -} -``` - -Now were are all set for the `main()` function: - -```C -int main(int argc, const char * argv[]) -{ - rcl_allocator_t allocator = rcl_get_default_allocator(); - rclc_support_t support; - rcl_ret_t rc; - - // create init_options - rc = rclc_support_init(&support, argc, argv, &allocator); - if (rc != RCL_RET_OK) { - printf("Error rclc_support_init.\n"); - return -1; - } -``` - -First rcl is initialized with the `rclc_support_init` using the default `allocator`. The rclc-support objects are saved in `support`. Next, a node `my_node` with the name `node_0` and namespace `executor_examples` is created with: - -```C -// create rcl_node - rcl_node_t my_node; - rc = rclc_node_init_default(&my_node, "node_0", "executor_examples", &support); - if (rc != RCL_RET_OK) { - printf("Error in rclc_node_init_default\n"); - return -1; - } -``` - -A publisher `my_string_pub`, which publishes a string message and its corresponding timer `my_string_timer` with a 100ms period is created like this: - -```C -// create a publisher 1 -// - topic name: 'topic_0' -// - message type: std_msg::msg::String -const char * topic_name = "topic_0"; -const rosidl_message_type_support_t * my_type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String); - -rc = rclc_publisher_init_default(&my_string_pub, &my_node, my_type_support, topic_name); -if (RCL_RET_OK != rc) { - printf("Error in rclc_publisher_init_default %s.\n", topic_name); - return -1; -} - -// create timer 1 -// - publishes 'my_string_pub' every 'timer_timeout' ms -rcl_timer_t my_string_timer; -const unsigned int timer_timeout = 100; -rc = rclc_timer_init_default(&my_string_timer, &support, RCL_MS_TO_NS(timer_timeout), my_timer_string_callback); -if (rc != RCL_RET_OK) { - printf("Error in rclc_timer_init_default.\n"); - return -1; -} else { - printf("Created timer 'my_string_timer' with timeout %d ms.\n", timer_timeout); -} -``` - -Note that the previously defined `my_timer_string_callback` is connected to this timer. -Likewise, a second publisher `my_int_pub, which publishes an int message and its corresponding timer` my_int_timer` with 1000ms period, is created like this: - -```C -// create publisher 2 - // - topic name: 'topic_1' - // - message type: std_msg::msg::Int - const char * topic_name_1 = "topic_1"; - const rosidl_message_type_support_t * my_int_type_support = - ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); - rc = rclc_publisher_init_default(&my_int_pub, &my_node, my_int_type_support, topic_name_1); - if (RCL_RET_OK != rc) { - printf("Error in rclc_publisher_init_default %s.\n", topic_name_1); - return -1; - } - - // create timer 2 - // - publishes 'my_int_pub' every 'timer_int_timeout' ms - rcl_timer_t my_int_timer; - const unsigned int timer_int_timeout = 10 * timer_timeout; - rc = rclc_timer_init_default(&my_int_timer, &support, RCL_MS_TO_NS(timer_int_timeout), my_timer_int_callback); - if (rc != RCL_RET_OK) { - printf("Error in rclc_timer_init_default.\n"); - return -1; - } else { - printf("Created timer with timeout %d ms.\n", timer_int_timeout); - } -``` - -Note that the `my_timer_int_callback` is connected to the `my_int_timer`. The data variables used for the publisher messages in the timer callbacks need to be initialized first: - -```C -std_msgs__msg__Int32__init(&int_pub_msg); -int_pub_value = 0; -string_pub_value = 0; -``` - -The first subscription `my_string_sub` is created with the function `rcl_subscription_init` because we change the quality-of-service parameter to 'last-is-best'. That is, a new message will overwrite the older message if it has not been processed by the subscription. Also the message `string_sub_msg` needs to be initialized. - -```C -// create subscription 1 - rcl_subscription_t my_string_sub = rcl_get_zero_initialized_subscription(); - rcl_subscription_options_t my_subscription_options = rcl_subscription_get_default_options(); - my_subscription_options.qos.depth = 0; // qos: last is best = register semantics - rc = rcl_subscription_init(&my_string_sub, &my_node, my_type_support, topic_name, &my_subscription_options); - - if (rc != RCL_RET_OK) { - printf("Failed to create subscriber %s.\n", topic_name); - return -1; - } else { - printf("Created subscriber %s:\n", topic_name); - } - // initialize subscription message - std_msgs__msg__String__init(&string_sub_msg); -``` - -The second subscription `my_int_sub` is created with the rclc convenience function `rclc_subscription_default` and the message `int_sub_msg` is properly initialized. - -```C -// create subscription 2 - rcl_subscription_t my_int_sub = rcl_get_zero_initialized_subscription(); - rc = rclc_subscription_init_default(&my_int_sub, &my_node, my_int_type_support, topic_name_1); - if (rc != RCL_RET_OK) { - printf("Failed to create subscriber %s.\n", topic_name_1); - return -1; - } else { - printf("Created subscriber %s:\n", topic_name_1); - } - // initialize subscription message - std_msgs__msg__Int32__init(&int_sub_msg); -``` - -In this example, we are using two executors, one to schedule the publishers, and one to schedule the subscriptions: - -```C -rclc_executor_t executor_pub; -rclc_executor_t executor_sub; -``` - -The executor `executor_pub` is first created with `rclc_executor_get_zero_initialized_executor()` and has two handles (aka 2 timers). - -```C -// Executor for publishing messages - unsigned int num_handles_pub = 2; - printf("Executor_pub: number of DDS handles: %u\n", num_handles_pub); - executor_pub = rclc_executor_get_zero_initialized_executor(); - rclc_executor_init(&executor_pub, &support.context, num_handles_pub, &allocator); - - rc = rclc_executor_add_timer(&executor_pub, &my_string_timer); - if (rc != RCL_RET_OK) { - printf("Error in rclc_executor_add_timer 'my_string_timer'.\n"); - } - - rc = rclc_executor_add_timer(&executor_pub, &my_int_timer); - if (rc != RCL_RET_OK) { - printf("Error in rclc_executor_add_timer 'my_int_timer'.\n"); - } -``` - -Both timers are added to the exececutor with the function `rclc_executor_add_timer`: - -```C -rc = rclc_executor_add_timer(&executor_pub, &my_string_timer); -if (rc != RCL_RET_OK) { - printf("Error in rclc_executor_add_timer 'my_string_timer'.\n"); -} - -rc = rclc_executor_add_timer(&executor_pub, &my_int_timer); -if (rc != RCL_RET_OK) { - printf("Error in rclc_executor_add_timer 'my_int_timer'.\n"); -} -``` - -Also the second publisher has two handles, the two subscriptions: - -```C -unsigned int num_handles_sub = 2; -printf("Executor_sub: number of DDS handles: %u\n", num_handles_sub); -executor_sub = rclc_executor_get_zero_initialized_executor(); -rclc_executor_init(&executor_sub, &support.context, num_handles_sub, &allocator); -``` - -Which are added with the function `rclc_executor_add_subscription`: - -```C -// add subscription to executor -rc = rclc_executor_add_subscription( - &executor_sub, &my_string_sub, &string_sub_msg, - &my_string_subscriber_callback, - ON_NEW_DATA); -if (rc != RCL_RET_OK) { - printf("Error in rclc_executor_add_subscription 'my_string_sub'. \n"); -} - -// add int subscription to executor -rc = rclc_executor_add_subscription( - &executor_sub, &my_int_sub, &int_sub_msg, - &my_int_subscriber_callback, - ON_NEW_DATA); -if (rc != RCL_RET_OK) { - printf("Error in rclc_executor_add_subscription 'my_int_sub'. \n"); -} -``` - -The trigger condition of the executor, which publishes messages, shall execute when any timer is ready. This can be configured with the function `rclc_executor_set_trigger` and the parameter `rclc_executor_trigger_any`. -While the executor for the subscriptions shall only execute if both messages have arrived. Therefore the trigger parameter `rclc_executor_trigger_any` can be used: - -```C -rc = rclc_executor_set_trigger(&executor_pub, rclc_executor_trigger_any, NULL); -rc = rclc_executor_set_trigger(&executor_sub, rclc_executor_trigger_all, NULL); -``` - -Finally, the executors spin-some functions can be started. The sleep-time between the executors is intended for communication time for DDS. - -```C -for (unsigned int i = 0; i < 100; i++) { - // timeout specified in ns (here: 1s) - rclc_executor_spin_some(&executor_pub, 1000 * (1000 * 1000)); - usleep(1000); // 1ms - rclc_executor_spin_some(&executor_sub, 1000 * (1000 * 1000)); -} -``` - -This example is concluded with the clean-up code: - -```C -// clean up -rc = rclc_executor_fini(&executor_pub); -rc += rclc_executor_fini(&executor_sub); -rc += rcl_publisher_fini(&my_string_pub, &my_node); -rc += rcl_publisher_fini(&my_int_pub, &my_node); -rc += rcl_timer_fini(&my_string_timer); -rc += rcl_timer_fini(&my_int_timer); -rc += rcl_subscription_fini(&my_string_sub, &my_node); -rc += rcl_subscription_fini(&my_int_sub, &my_node); -rc += rcl_node_fini(&my_node); -rc += rclc_support_fini(&support); - -std_msgs__msg__Int32__fini(&int_pub_msg); -std_msgs__msg__String__fini(&string_sub_msg); -std_msgs__msg__Int32__fini(&int_sub_msg); - -if (rc != RCL_RET_OK) { - printf("Error while cleaning up!\n"); - return -1; -} -return 0; -} -``` - -In case the default trigger conditions are not sufficient, then the user can define custom logic conditions. -The source code of the custom-programmed trigger condition has already been presented. -The following code will setup the executor accordingly: - -```C - pub_trigger_object_t comm_obj_pub; - comm_obj_pub.timer1 = &my_string_timer; - comm_obj_pub.timer2 = &my_int_timer; - - sub_trigger_object_t comm_obj_sub; - comm_obj_sub.sub1 = &my_string_sub; - comm_obj_sub.sub2 = &my_int_sub; - - rc = rclc_executor_set_trigger(&executor_pub, pub_trigger, &comm_obj_pub); - rc = rclc_executor_set_trigger(&executor_sub, sub_trigger, &comm_obj_sub); -``` - -The custom structs `pub_trigger_object_t` are used to save the pointer of the handles. The timers `my_string_timer` and `my_int_timer` for the publishing executor; and, likewise, the subscriptions `my_string_sub` and `my_int_sub` for the subscribing executor. The configuration is done also with the `rclc_executor_set_trigger` by passing the trigger function and the trigger object, e.g. `pub_trigger` and `comm_obj_pub` for the `executor_pub`, respectivly. - -The complete source code of this example can be found in the file [rclc-examples/example_executor_trigger.c](https://github.com/ros2/rclc/rclc_examples/example_executor_trigger.c). diff --git a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md new file mode 100644 index 00000000..ed766f5d --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md @@ -0,0 +1,46 @@ +--- +title: micro-ROS utilities +permalink: /docs/tutorials/programming_rcl_rclc/micro-ROS/ +--- + +## micro-ROS features + + +- Custom transport +rmw_ret_t rmw_uros_set_custom_transport( + bool framing, + void * args, + open_custom_func open_cb, + close_custom_func close_cb, + write_custom_func write_cb, + read_custom_func read_cb); + + +- Time sync + +```C + +bool rmw_uros_epoch_synchronized(); +int64_t rmw_uros_epoch_millis(); +int64_t rmw_uros_epoch_nanos(); +rmw_ret_t rmw_uros_sync_session(const int timeout_ms); + +``` + +- Ping agent + +rmw_ret_t rmw_uros_ping_agent(const int timeout_ms, const uint8_t attempts); + + +- Init options ?? + +- Discovery ?? + +- Continous serialization ?? +void rmw_uros_set_continous_serialization_callbacks( +rmw_publisher_t * publisher, +rmw_uros_continous_serialization_size size_cb, +rmw_uros_continous_serialization serialization_cb); + + +// Add publisher / service / client timeout here? \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/node/node.md b/_docs/tutorials/programming_rcl_rclc/node/node.md new file mode 100644 index 00000000..a4771e32 --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/node/node.md @@ -0,0 +1,70 @@ +--- +title: Nodes +permalink: /docs/tutorials/programming_rcl_rclc/node/ +--- + +## Nodes + +## Creating a node + +// TODO: explain reliable vs best_effort + +- Create a node: +```C +// TODO: explain allocator and support +rcl_allocator_t allocator = rcl_get_default_allocator(); +rclc_support_t support; + +rcl_ret_trc = rclc_support_init(&support, argc, argv, &allocator); +if (rc != RCL_RET_OK) { + printf("Error creating support object\n"); + return -1; +} + +// Node object +rcl_node_t node; +const char * node_name = "test_node"; +const char * namespace = "test_namespace"; +rc = rclc_node_init_default(&node, node_name, namespace, &support); +if (rc != RCL_RET_OK) { + // Handle error + printf("Error creating node\n"); + return -1; +} + +``` + +## Destroy a node + +```C +// Destroy a node +rc = rcl_node_fini(&node); + +if (rc == RCL_RET_OK) { + printf("Error destroying node\n"); +} +``` + +## Node options + + +```C +// TODO: explain allocator and support +rcl_allocator_t allocator = rcl_get_default_allocator(); +rclc_support_t support; + +// Node object +rcl_node_t node; +const char * node_name = "test_node"; +const char * namespace = "test_namespace"; + +// TODO: explain options +rcl_node_options_t node_options = rcl_node_get_default_options(); + +rc = rclc_node_init_default(&node, node_name, namespace, &support, &node_options); +if (rc != RCL_RET_OK) { + printf("Error creating node\n"); + return -1; +} + +``` \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/overview/index.md b/_docs/tutorials/programming_rcl_rclc/overview/index.md new file mode 100644 index 00000000..ac892992 --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/overview/index.md @@ -0,0 +1,17 @@ +--- +title: Overview +permalink: /docs/tutorials/programming_rcl_rclc/overview/ +redirect_from: + - /docs/tutorials/programming_rcl_rclc/ +--- + + + +In this tutorials, you'll learn the basics of the micro-ROS C API. The major concepts (publishers, subscriptions, services,timers, ...) are identical with ROS 2. They even rely on the *same* implementation, as the micro-ROS C API is based on the ROS 2 client support library (rcl), enriched with a set of convenience functions by the package [rclc](https://github.com/ros2/rclc/). That is, rclc does not add a new layer of types on top of rcl (like rclcpp and rclpy do) but only provides functions that ease the programming with the rcl types. New types are introduced only for concepts that are missing in rcl, such as the concept of an executor. + +* [**Node**](../node/) +* [**Publishers and Subscriptions**](../pub_sub/) +* [**Services**](../service/) +* [**Parameters**](../parameters/) +* [**QoS**](../qos/) +* [**micro-ROS Utils**](../micro-ROS/) diff --git a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md new file mode 100644 index 00000000..ce73da1a --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md @@ -0,0 +1,124 @@ +--- +title: Parameters +permalink: /docs/tutorials/programming_rcl_rclc/parameters/ +--- + +## Parameters +// TODO: add link to example + +## Creating a parameter server + + +```C +// Parameter server object +rclc_parameter_server_t param_server; + +// Initialize parameter server with default configuration +rcl_ret_t rc = rclc_parameter_server_init_default(¶m_server, &node); + +if (RCL_RET_OK != rc) { + printf("Error creating parameter server\n"); + return -1; +} + +// Configure executor with atleast RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER handles +rclc_executor_t executor; +rclc_executor_init(&executor, &support.context, RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER, &allocator); + +// Add parameter server to executor +rc = rclc_executor_add_parameter_server(&executor, ¶m_server, NULL); +``` + +- Parameter changed callback + +When adding the paramater server to the executor, a callback for parameter changes can be passed. +This callback will be called after a parameter value is modified. + +A pointer to the changed parameter is passed as first and only argument. Example: +```C +void on_parameter_changed(Parameter * param) +{ + // Get parameter name + printf("Parameter %s modified.", param->name.data); + + // Get parameter type + switch (param->value.type) + { + // Get parameter value acording type + case RCLC_PARAMETER_BOOL: + printf(" New value: %d (bool)", param->value.bool_value); + break; + case RCLC_PARAMETER_INT: + printf(" New value: %ld (int)", param->value.integer_value); + break; + case RCLC_PARAMETER_DOUBLE: + printf(" New value: %f (double)", param->value.double_value); + break; + default: + break; + } + + printf("\n"); +} + +// Add parameter server to executor including defined callback +rc = rclc_executor_add_parameter_server(&executor, ¶m_server, on_parameter_changed); +``` + +// TODO: explain options on creation +// TODO: explain destruction +// TODO: explain memory requirements + +## Add a parameter + +// TODO: improve explanation of types + +- Bool parameter +```C +const char * parameter_name = "parameter_bool"; +bool param_value = true; + +// Add parameter to the server +rcl_ret_t rc = rclc_add_parameter(¶m_server, parameter_name, RCLC_PARAMETER_BOOL); + +// Set parameter value (Triggers parameter change callback) +rc = rclc_parameter_set_bool(¶m_server, parameter_name, param_value); + +// Get parameter value on param_value +rc = rclc_parameter_get_bool(¶m_server, "param1", ¶m_value); + +if (RCL_RET_OK != rc) { + // Handle error + return -1; +} +``` + +- Integer parameter +```C +const char * parameter_name = "parameter_int"; +int param_value = 100; + +// Add parameter to the server +rcl_ret_t rc = rclc_add_parameter(¶m_server, parameter_name, RCLC_PARAMETER_INT); + +// Set parameter value +rc = rclc_parameter_set_int(¶m_server, parameter_name, param_value); + +// Get parameter value on param_value +rc = rclc_parameter_get_int(¶m_server, parameter_name, ¶m_value); +``` + +- Double parameter +```C +const char * parameter_name = "parameter_double"; +double param_value = 0.15; + +// Add parameter to the server +rcl_ret_t rc = rclc_add_parameter(¶m_server, parameter_name, RCLC_PARAMETER_DOUBLE); + +// Set parameter value +rc = rclc_parameter_set_double(¶m_server, parameter_name, param_value); + +// Get parameter value on param_value +rc = rclc_parameter_get_double(¶m_server, parameter_name, ¶m_value); +``` diff --git a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md new file mode 100644 index 00000000..914b38f6 --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md @@ -0,0 +1,240 @@ +--- +title: Publishers and Subscriptions +permalink: /docs/tutorials/programming_rcl_rclc/pub_sub/ +--- + +## Publisher + +### Initialization + +Starting from a code where RCL is initialized and a micro-ROS node is created, there are tree ways to initialize a publisher: + +// TODO: explain and link diferences between each approach on QoS section + +- Reliable publisher: + ```C + // Publisher object + rcl_publisher_t publisher; + const char * topic_name = "test_topic"; + + // TODO: explain type_support? + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + + // Create a reliable rcl publisher. + rcl_ret_t rc = rclc_publisher_init_default(&publisher, &node, &type_support, &topic_name); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + + Reliable publishers will wait for confirmation for each published message, which leads to blocking `rcl_publish` calls. The `rmw-microxrcedds` offer an API to configure the acknowledgement timeout for each publisher: + + ```C + // Set confirmation timeout in milliseconds + int publish_timeout = 1000; + rc = rmw_uros_set_publisher_session_timeout(&publisher, publish_timeout); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + + The default value for all publishers is configured at compilation time by the cmake variable `RMW_UXRCE_PUBLISH_RELIABLE_TIMEOUT`. + +- Best effort publisher: + + // TODO: explain in QoS section? + Publish the message without reception confirmation, allowing a faster publish rate. + + ```C + // Publisher object + rcl_publisher_t publisher; + const char * topic_name = "test_topic"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + + // Creates an rcl publisher with quality-of-service option best effort + rcl_ret_t rc = rclc_publisher_init_best_effort(&publisher, &node, &type_support, &topic_name); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + +- Add QoS API + + ```C + // Publisher object + rcl_publisher_t publisher; + const char * topic_name = "test_topic"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + + // Set publisher QoS + const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default; + + // Creates an rcl publisher with customized quality-of-service options + rcl_ret_t rc = rclc_publisher_init(&publisher, &node, &type_support, &topic_name, qos_profile); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + +## Publish a message + +// TODO: explain message memory allocation and link to tutorial +// TODO: explain periodic publication and link to timers +```C +// Int32 message object +std_msgs__msg__Int32 msg; + +// Set message value +msg.data = 0; + +// Publish message +rcl_ret_t rc = rcl_publish(&publisher, &msg, NULL); +if (rc != RCL_RET_OK) { + ... // Handle error + return -1; +} +``` + +Note: `rcl_publish` is thread safe and can be called from multiple threads + +## Destroy a publisher + +Deallocates memory +After calling, the node will no longer be advertising that it is publishing +on this topic (assuming this is the only publisher on this topic). + +```C +// Destroy publisher +rcl_ret_t rc = rcl_publisher_fini(&publisher, &node); + +if (rc == RCL_RET_OK) { + ... // Handle error + return -1; +} +``` + +Note: Publishers needs to be destroyed before its containing node + +## Subscription + +### Initialization + +The suscriptor initialization is almost identical to the publisher one: + +- Reliable (default): + ```C + // Subscription object + rcl_subscription_t subscriber; + const char * topic_name = "test_topic"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + + // Set client QoS + const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default; + + // Initialize subscriber with default configuration + rcl_ret_t rc = rclc_subscription_init_default(&subscriber, &node, &type_support, &topic_name); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + +- Best effort: + + ```C + // Subscription object + rcl_subscription_t subscriber; + const char * topic_name = "test_topic"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + + // Initialize best effort subscriber + rcl_ret_t rc = rclc_subscription_init_best_effort(&subscriber, &node, &type_support, &topic_name); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + +- Add QoS API + + ```C + // Subscription object + rcl_subscription_t subscriber; + const char * topic_name = "test_topic"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + + // Set client QoS + const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default; + + // Initialize server with customized quality-of-service options + rcl_ret_t rc = rclc_subscription_init(&subscriber, &node, &type_support, &topic_name, qos_profile); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + +### Callbacks + +// TODO: explain message memory allocation and link to tutorial + +```C +void subscription_callback(const void * msgin) +{ + // Cast received message to used type + const std_msgs__msg__Int32 * msg = (const std_msgs__msg__Int32 *)msgin; + + // Process message + printf("Received: %d\n", msg->data); +} +``` + +Once the subscriber and the executor are initialized, the subscriber callback must be added to the executor to receive incoming publications once the executor is spinning: + +```C +// Message object +std_msgs__msg__Int32 msg; + +// Add subscription to the executor +rcl_ret_t rc = rclc_executor_add_subscription(&executor, &subscriber, &msg, &subscription_callback, ON_NEW_DATA); + +if (RCL_RET_OK != rc) { + ... // Handle error + return -1; +} +``` + +### Destroy a subscriber +Destroy subscriber (Publisher needs to be destroyed manually before the node) +Destroys any automatically created infrastructure and deallocates memory. + +```C +// Destroy +rcl_ret_t rc = rcl_publisher_fini(&subscriber, &node); + +if (rc == RCL_RET_OK) { + printf("Published message with value: %d\n", msg.data); +} +``` diff --git a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md new file mode 100644 index 00000000..a30b33a4 --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md @@ -0,0 +1,45 @@ +--- +title: Quality of service +permalink: /docs/tutorials/programming_rcl_rclc/qos/ +--- + +## QoS + +```C + +/// ROS MiddleWare quality of service profile. +typedef struct RMW_PUBLIC_TYPE rmw_qos_profile_t +{ + enum rmw_qos_history_policy_t history; + /// Size of the message queue. + size_t depth; + /// Reliabiilty QoS policy setting + enum rmw_qos_reliability_policy_t reliability; + /// Durability QoS policy setting + enum rmw_qos_durability_policy_t durability; + /// The period at which messages are expected to be sent/received + struct rmw_time_t deadline; + /// The age at which messages are considered expired and no longer valid + struct rmw_time_t lifespan; + /// Liveliness QoS policy setting + enum rmw_qos_liveliness_policy_t liveliness; + /// The time within which the RMW node or publisher must show that it is alive + struct rmw_time_t liveliness_lease_duration; + + /// If true, any ROS specific namespacing conventions will be circumvented. + /** + * In the case of DDS and topics, for example, this means the typical + * ROS specific prefix of `rt` would not be applied as described here: + * + * http://design.ros2.org/articles/topic_and_service_names.html#ros-specific-namespace-prefix + * + * This might be useful when trying to directly connect a native DDS topic + * with a ROS 2 topic. + */ + bool avoid_ros_namespace_conventions; +} rmw_qos_profile_t; + +``` + + +// TODO: explain difference between reliable and best effort \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/service/services.md b/_docs/tutorials/programming_rcl_rclc/service/services.md new file mode 100644 index 00000000..0f125abe --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/service/services.md @@ -0,0 +1,254 @@ +--- +title: Services +permalink: /docs/tutorials/programming_rcl_rclc/service/ +--- + +ROS 2 services are another communication mechanism between nodes. Services implement a client-server paradigm based on ROS 2 messages and types. Further information about ROS 2 services can be found [here](https://index.ros.org/doc/ros2/Tutorials/Services/Understanding-ROS2-Services/) + +Ready to use code related to this tutorial can be found in [`micro-ROS-demos/rclc/addtwoints_server`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_server/main.c) and [`micro-ROS-demos/rclc/addtwoints_client`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_client/main.c) folders. Fragments of code from this examples are used on this tutorial. + +## Service server + +### Initialization +Starting from a code where RCL is initialized and a micro-ROS node is created, there are tree ways to initialize a service server: + +// TODO: explain and link diferences between each approach on QoS section + +- Reliable (default): + + ```C + // Service server object + rcl_service_t service; + const char * service_name = "/addtwoints"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + + // Initialize server with default configuration + rcl_ret_t rc = rclc_service_init_default(&service, &node, type_support, service_name); + + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` + + // TODO: Add timeout API for reliable service + +- Best effort: + + ```C + // Service server object + rcl_service_t service; + const char * service_name = "/addtwoints"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + + // Initialize server with default configuration + rcl_ret_t rc = rclc_service_init_best_effort(&service, &node, type_support, service_name); + + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` + +- Custom QoS: + + ```C + // Service server object + rcl_service_t service; + const char * service_name = "/addtwoints"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + + // Set service QoS + const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_services_default; + + // Initialize server with customized quality-of-service options + rcl_ret_t rc = rclc_service_init(&service, &node, type_support, service_name, qos_profile); + + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` + +### Callback + +// TODO: add and explain function prototype? +Once a request arrives, the executor will call the configured callback with the request and response messages as arguments. +The request message contains the values sended by the client, the response_msg should be modified here as it will be delivered after the callback returns. + +Using `AddTwoInts.srv` type definition as an example: + +```C +int64 a +int64 b +--- +int64 sum +``` + +The client request message will contain two integers `a` and `b`, and expectes the `sum` of them as a response: + +```C +void service_callback(const void * request_msg, void * response_msg){ + // Cast messages to expected types + example_interfaces__srv__AddTwoInts_Request * req_in = (example_interfaces__srv__AddTwoInts_Request *) request_msg; + example_interfaces__srv__AddTwoInts_Response * res_in = (example_interfaces__srv__AddTwoInts_Response *) response_msg; + + // Handle request message and set the response message values + printf("Client requested sum of %d and %d.\n", (int) req_in->a, (int) req_in->b); + res_in->sum = req_in->a + req_in->b; +} +``` + +Note that it is neccesary to cast each message to the expected type + +Once the service and the executor are initialized, the service callback must be added to the executor in order to process incoming requests once the executor is spinning: + +```C +// Service message objects +example_interfaces__srv__AddTwoInts_Response response_msg; +example_interfaces__srv__AddTwoInts_Request request_msg; + +// Add server callback to the executor +rc = rclc_executor_add_service(&executor, &service, &request_msg, &response_msg, service_callback); + +// Spin executor to receive requests +rclc_executor_spin(&executor); + +if (rc != RCL_RET_OK) { + ... // Handle error + return -1; +} + +// TODO ?? +// rclc_executor_add_service_with_context +// rclc_executor_add_service_with_request_id +``` + +## Service Client + +### Initialization +The service client initialization is almost identical to the server one: + +- Reliable (default): + + ```C + // Service client object + rcl_client_t client; + const char * service_name = "/addtwoints"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + + // Initialize client with default configuration + rcl_ret_t rc = rclc_client_init_default(&client, &node, type_support, service_name); + + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` + + // TODO: Add timeout API for reliable service + +- Best effort: + + ```C + // Service client object + rcl_client_t client; + const char * service_name = "/addtwoints"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + + // Initialize client with default configuration + rcl_ret_t rc = rclc_client_init_best_effort(&client, &node, type_support, service_name); + + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` + +- Custom QoS: + + ```C + // Service client object + rcl_client_t client; + const char * service_name = "/addtwoints"; + + // Get message type support + const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + + // Set client QoS + const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_services_default; + + // Initialize server with customized quality-of-service options + rcl_ret_t rc = rclc_client_init(&client, &node, type_support, service_name, qos_profile); + + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` + +### Callback +The executor is responsible to call the configured callback when the service response arrives. +The function will have the response message as its only argument, containing the values sended by the server. + +It is neccesary to cast the response message to the expected type. Example: +```C +void client_callback(const void * response_msg){ + // Cast response message to expected type + example_interfaces__srv__AddTwoInts_Response * msgin = (example_interfaces__srv__AddTwoInts_Response * ) response_msg; + + // Handle response message + printf("Received service response %ld + %ld = %ld\n", req.a, req.b, msgin->sum); +} +``` + +Once the client and the executor are initialized, the client callback must be added to the executor in order to receive the service response once the executor is spinning: + +```C +// Response message object +example_interfaces__srv__AddTwoInts_Response res; + +// Add client callback to the executor +rcl_ret_t rc = rclc_executor_add_client(&executor, &client, &res, client_callback); + +if (rc != RCL_RET_OK) { + ... // Handle error + return -1; +} + +// Spin executor to receive requests +rclc_executor_spin(&executor); +``` + +### Send a request +Once the service client and server are configured, the service client can perform a request and spin the executor to get the reply. +Following the example on `AddTwoInts.srv`: + +```C +// Request message object (Must match initialized client type support) +example_interfaces__srv__AddTwoInts_Request request_msg; + +// Initialize request message memory and set its values +example_interfaces__srv__AddTwoInts_Request__init(&request_msg); +request_msg.a = 24; +request_msg.b = 42; + +// Sequence number of the request (Populated in rcl_send_request) +int64_t sequence_number; + +// Send request +rcl_send_request(&client, &request_msg, &sequence_number); + +// Spin the executor to get the response +rclc_executor_spin(&executor); +``` diff --git a/_includes/docs_nav.html b/_includes/docs_nav.html index 3b7e2ac8..49426a04 100644 --- a/_includes/docs_nav.html +++ b/_includes/docs_nav.html @@ -25,6 +25,9 @@ {% if page.path contains "_docs/tutorials" and section.title contains "Tutorials" %} {% assign should_show_this_menu = true %} {% endif %} + {% if page.path contains "_docs/tutorials" and section.title contains "Programming" %} + {% assign should_show_this_menu = true %} + {% endif %} {% if page.path contains "_docs/tutorials" and section.title == "Demos" %} {% assign should_show_this_menu = true %} {% endif %} From 090f4358c6d647d6f16464bc2994ca1cda9acd2e Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Thu, 5 Aug 2021 14:49:14 +0200 Subject: [PATCH 02/14] Update parameters --- .../parameters/parameters.md | 114 +++++++++++++----- .../programming_rcl_rclc/pub_sub/pub_sub.md | 52 ++++---- .../programming_rcl_rclc/service/services.md | 30 ++++- 3 files changed, 138 insertions(+), 58 deletions(-) diff --git a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md index ce73da1a..b676cf12 100644 --- a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md +++ b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md @@ -1,38 +1,79 @@ --- -title: Parameters +title: Parameter server permalink: /docs/tutorials/programming_rcl_rclc/parameters/ --- -## Parameters -// TODO: add link to example +ROS 2 parameter allow the user to create variables on a node and manipulate/read them with different ROS2 commands. Further information about ROS 2 parameters can be found [here](https://docs.ros.org/en/galactic/Tutorials/Parameters/Understanding-ROS2-Parameters.html) -## Creating a parameter server - +Ready to use code related to this tutorial can be found in [`micro-ROS-demos/rclc/parameter_server`](https://github.com/micro-ROS/micro-ROS-demos/blob/galactic/rclc/parameter_server/main.c) folder. Fragments of code from this example is used on this tutorial. -```C -// Parameter server object -rclc_parameter_server_t param_server; +Note: micro-ROS parameter server is only supported on ROS2 galactic distribution -// Initialize parameter server with default configuration -rcl_ret_t rc = rclc_parameter_server_init_default(¶m_server, &node); +## Initialization -if (RCL_RET_OK != rc) { - printf("Error creating parameter server\n"); - return -1; -} +- Default initialization: + ```C + // Parameter server object + rclc_parameter_server_t param_server; -// Configure executor with atleast RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER handles -rclc_executor_t executor; -rclc_executor_init(&executor, &support.context, RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER, &allocator); + // Initialize parameter server with default configuration + rcl_ret_t rc = rclc_parameter_server_init_default(¶m_server, &node); -// Add parameter server to executor -rc = rclc_executor_add_parameter_server(&executor, ¶m_server, NULL); -``` + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` -- Parameter changed callback +// TODO: explain options +- Custom options: + ```C + // Parameter server object + rclc_parameter_server_t param_server; -When adding the paramater server to the executor, a callback for parameter changes can be passed. -This callback will be called after a parameter value is modified. + // Define parameter server options + const rclc_parameter_options_t options = { .notify_changed_over_dds = true, .max_params = 4 }; + + // Initialize parameter server with configured options + rcl_ret_t rc = rclc_parameter_server_init_with_option(¶m_server, &node, &options); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + +- Memory and executor requirements: + The variable `RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER` defines the RCLC executor handles required for a parameter server. + This needs to be taken into account when initializing the executor and on the colcon memory configuration of the `rmw-microxredds` package, which will need atleast 4 services and 1 publisher: + + ```C + # colcon.meta example with minimum memory requirements to use parameter server + { + "names": { + "rmw_microxrcedds": { + "cmake-args": [ + "-DRMW_UXRCE_MAX_NODES=1", + "-DRMW_UXRCE_MAX_PUBLISHERS=1", + "-DRMW_UXRCE_MAX_SUBSCRIPTIONS=0", + "-DRMW_UXRCE_MAX_SERVICES=4", + "-DRMW_UXRCE_MAX_CLIENTS=0" + ] + } + } + } + ``` + + ```C + // Executor init example with the minimum RCLC executor handles required + rclc_executor_t executor = rclc_executor_get_zero_initialized_executor(); + rc = rclc_executor_init(&executor, &support.context, RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER, &allocator); + ``` + +## Callback + +When adding the paramater server to the executor, a callback can be configured. +This callback will be executed after a parameter value is modified. A pointer to the changed parameter is passed as first and only argument. Example: ```C @@ -60,14 +101,19 @@ void on_parameter_changed(Parameter * param) printf("\n"); } - +``` +Once the parameter server and the executor are initialized, the parameter server must be added to the executor in order to accept parameters commands from ROS2: +```C // Add parameter server to executor including defined callback rc = rclc_executor_add_parameter_server(&executor, ¶m_server, on_parameter_changed); ``` -// TODO: explain options on creation -// TODO: explain destruction -// TODO: explain memory requirements +Note that this callback is optional as its just an event information for the user. To use the parameter server without a callback: +```C +// Add parameter server to executor without callback +rc = rclc_executor_add_parameter_server(&executor, ¶m_server, NULL); +``` + ## Add a parameter @@ -88,7 +134,7 @@ rc = rclc_parameter_set_bool(¶m_server, parameter_name, param_value); rc = rclc_parameter_get_bool(¶m_server, "param1", ¶m_value); if (RCL_RET_OK != rc) { - // Handle error + ... // Handle error return -1; } ``` @@ -122,3 +168,15 @@ rc = rclc_parameter_set_double(¶m_server, parameter_name, param_value); // Get parameter value on param_value rc = rclc_parameter_get_double(¶m_server, parameter_name, ¶m_value); ``` + +## Destroy the parameter server + +```C +// Delete parameter server +rcl_ret_t rc = rclc_parameter_server_fini(¶m_server, &node); + +if (rc == RCL_RET_OK) { + ... // Handle error + return -1; +} +``` \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md index 914b38f6..236a433c 100644 --- a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md +++ b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md @@ -3,25 +3,28 @@ title: Publishers and Subscriptions permalink: /docs/tutorials/programming_rcl_rclc/pub_sub/ --- +ROS 2 publishers and subscribers are the basic communication mechanism between nodes using topics. Further information about ROS 2 publish–subscribe pattern can be found [here](https://docs.ros.org/en/foxy/Tutorials/Topics/Understanding-ROS2-Topics.html) + +Ready to use code related to this tutorial can be found in [`micro-ROS-demos/rclc/int32_publisher`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_publisher/main.c) and [`micro-ROS-demos/rclc/int32_subscriber`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_subscriber/main.c) folders. Fragments of code from this examples are used on this tutorial. + ## Publisher ### Initialization -Starting from a code where RCL is initialized and a micro-ROS node is created, there are tree ways to initialize a publisher: +Starting from a code where RCL is initialized and a micro-ROS node is created, there are tree ways to initialize a publisher depending on the desired quality-of-service configuration: // TODO: explain and link diferences between each approach on QoS section -- Reliable publisher: +- Reliable: ```C // Publisher object rcl_publisher_t publisher; const char * topic_name = "test_topic"; - // TODO: explain type_support? // Get message type support const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); - // Create a reliable rcl publisher. + // Creates a reliable rcl publisher rcl_ret_t rc = rclc_publisher_init_default(&publisher, &node, &type_support, &topic_name); if (RCL_RET_OK != rc) { @@ -29,13 +32,14 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t return -1; } ``` - - Reliable publishers will wait for confirmation for each published message, which leads to blocking `rcl_publish` calls. The `rmw-microxrcedds` offer an API to configure the acknowledgement timeout for each publisher: + + // TODO: move to micro-ROS features section? + Reliable publishers will wait for confirmation for each published message, which leads to blocking `rcl_publish` calls, `rmw-microxrcedds` offers an API to configure the acknowledgement timeout for each publisher: ```C // Set confirmation timeout in milliseconds - int publish_timeout = 1000; - rc = rmw_uros_set_publisher_session_timeout(&publisher, publish_timeout); + int ack_timeout = 1000; + rc = rmw_uros_set_publisher_session_timeout(&publisher, ack_timeout); if (RCL_RET_OK != rc) { ... // Handle error @@ -45,11 +49,7 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t The default value for all publishers is configured at compilation time by the cmake variable `RMW_UXRCE_PUBLISH_RELIABLE_TIMEOUT`. -- Best effort publisher: - - // TODO: explain in QoS section? - Publish the message without reception confirmation, allowing a faster publish rate. - +- Best effort: ```C // Publisher object rcl_publisher_t publisher; @@ -58,7 +58,7 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t // Get message type support const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); - // Creates an rcl publisher with quality-of-service option best effort + // Creates a best effort rcl publisher rcl_ret_t rc = rclc_publisher_init_best_effort(&publisher, &node, &type_support, &topic_name); if (RCL_RET_OK != rc) { @@ -67,7 +67,7 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t } ``` -- Add QoS API +- Custom QoS: ```C // Publisher object @@ -80,7 +80,7 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t // Set publisher QoS const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default; - // Creates an rcl publisher with customized quality-of-service options + // Creates a rcl publisher with customized quality-of-service options rcl_ret_t rc = rclc_publisher_init(&publisher, &node, &type_support, &topic_name, qos_profile); if (RCL_RET_OK != rc) { @@ -108,12 +108,12 @@ if (rc != RCL_RET_OK) { } ``` -Note: `rcl_publish` is thread safe and can be called from multiple threads +Note: `rcl_publish` is thread safe and can be called from multiple threads. ## Destroy a publisher Deallocates memory -After calling, the node will no longer be advertising that it is publishing +After finishing the publisher, the node will no longer be advertising that it is publishing on this topic (assuming this is the only publisher on this topic). ```C @@ -143,10 +143,7 @@ The suscriptor initialization is almost identical to the publisher one: // Get message type support const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); - // Set client QoS - const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default; - - // Initialize subscriber with default configuration + // Initialize a realiable subscriber rcl_ret_t rc = rclc_subscription_init_default(&subscriber, &node, &type_support, &topic_name); if (RCL_RET_OK != rc) { @@ -187,7 +184,7 @@ The suscriptor initialization is almost identical to the publisher one: // Set client QoS const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default; - // Initialize server with customized quality-of-service options + // Initialize a subscriber with customized quality-of-service options rcl_ret_t rc = rclc_subscription_init(&subscriber, &node, &type_support, &topic_name, qos_profile); if (RCL_RET_OK != rc) { @@ -198,8 +195,6 @@ The suscriptor initialization is almost identical to the publisher one: ### Callbacks -// TODO: explain message memory allocation and link to tutorial - ```C void subscription_callback(const void * msgin) { @@ -214,7 +209,7 @@ void subscription_callback(const void * msgin) Once the subscriber and the executor are initialized, the subscriber callback must be added to the executor to receive incoming publications once the executor is spinning: ```C -// Message object +// Message object to save publication data std_msgs__msg__Int32 msg; // Add subscription to the executor @@ -234,7 +229,8 @@ Destroys any automatically created infrastructure and deallocates memory. // Destroy rcl_ret_t rc = rcl_publisher_fini(&subscriber, &node); -if (rc == RCL_RET_OK) { - printf("Published message with value: %d\n", msg.data); +if (RCL_RET_OK != rc) { + ... // Handle error + return -1; } ``` diff --git a/_docs/tutorials/programming_rcl_rclc/service/services.md b/_docs/tutorials/programming_rcl_rclc/service/services.md index 0f125abe..de7638cc 100644 --- a/_docs/tutorials/programming_rcl_rclc/service/services.md +++ b/_docs/tutorials/programming_rcl_rclc/service/services.md @@ -33,8 +33,21 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t } ``` - // TODO: Add timeout API for reliable service + A reliable service server will wait for confirmation for each response sended, which can increase the blocking time of the executor spins, `rmw-microxrcedds` offers an API to configure the acknowledgement timeout for each service: + ```C + // Set confirmation timeout in milliseconds + int ack_timeout = 1000; + rc = rmw_uros_set_service_session_timeout(&service, ack_timeout); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + + The default value for all clients is configured at compilation time by the cmake variable `RMW_UXRCE_PUBLISH_RELIABLE_TIMEOUT`. + - Best effort: ```C @@ -154,8 +167,21 @@ The service client initialization is almost identical to the server one: } ``` - // TODO: Add timeout API for reliable service + A reliable service client will wait for confirmation for each request sended, which can increase the blocking time of the executor spins, `rmw-microxrcedds` offers an API to configure the acknowledgement timeout for each client: + + ```C + // Set confirmation timeout in milliseconds + int ack_timeout = 1000; + rc = rmw_uros_set_client_session_timeout(&client, ack_timeout); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + The default value for all clients is configured at compilation time by the cmake variable `RMW_UXRCE_PUBLISH_RELIABLE_TIMEOUT`. + - Best effort: ```C From bdfb88855411ef7384ee2c013c9724b2ab970c8c Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Fri, 6 Aug 2021 12:21:49 +0200 Subject: [PATCH 03/14] Update --- .../programming_rcl_rclc/executor/executor.md | 6 + .../programming_rcl_rclc/node/node.md | 146 ++++++++++++------ .../programming_rcl_rclc/overview/index.md | 1 + .../parameters/parameters.md | 15 +- .../programming_rcl_rclc/pub_sub/pub_sub.md | 40 ++--- .../programming_rcl_rclc/service/services.md | 12 ++ 6 files changed, 132 insertions(+), 88 deletions(-) create mode 100644 _docs/tutorials/programming_rcl_rclc/executor/executor.md diff --git a/_docs/tutorials/programming_rcl_rclc/executor/executor.md b/_docs/tutorials/programming_rcl_rclc/executor/executor.md new file mode 100644 index 00000000..c620c44b --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/executor/executor.md @@ -0,0 +1,6 @@ +--- +title: Executor and timers +permalink: /docs/tutorials/programming_rcl_rclc/micro-ROS/ +--- + +// TODO: Use existing tutorial if possible \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/node/node.md b/_docs/tutorials/programming_rcl_rclc/node/node.md index a4771e32..6593707f 100644 --- a/_docs/tutorials/programming_rcl_rclc/node/node.md +++ b/_docs/tutorials/programming_rcl_rclc/node/node.md @@ -3,68 +3,112 @@ title: Nodes permalink: /docs/tutorials/programming_rcl_rclc/node/ --- -## Nodes +ROS 2 nodes are the ground element on ROS2 ecosystem. They will contain communicate between each other using publishers, subscriptions, services, ... .Further information about ROS 2 nodes can be found [here](https://docs.ros.org/en/galactic/Tutorials/Understanding-ROS2-Nodes.html) -## Creating a node +// TODO: explain general micro-ROS initialization (allocator and support) +## Initialization -// TODO: explain reliable vs best_effort +- Create a node with default configuration: + ```C + // Initialize micro-ROS allocator + rcl_allocator_t allocator = rcl_get_default_allocator(); -- Create a node: -```C -// TODO: explain allocator and support -rcl_allocator_t allocator = rcl_get_default_allocator(); -rclc_support_t support; - -rcl_ret_trc = rclc_support_init(&support, argc, argv, &allocator); -if (rc != RCL_RET_OK) { - printf("Error creating support object\n"); - return -1; -} - -// Node object -rcl_node_t node; -const char * node_name = "test_node"; -const char * namespace = "test_namespace"; -rc = rclc_node_init_default(&node, node_name, namespace, &support); -if (rc != RCL_RET_OK) { - // Handle error - printf("Error creating node\n"); - return -1; -} + // Initialize support object + rclc_support_t support; + rcl_ret_t rc = rclc_support_init(&support, argc, argv, &allocator); -``` + // Create node object + rcl_node_t node; + const char * node_name = "test_node"; -## Destroy a node + // Node namespace (Can remain empty "") + const char * namespace = "test_namespace"; -```C -// Destroy a node -rc = rcl_node_fini(&node); + // Init default node + rc = rclc_node_init_default(&node, node_name, namespace, &support); + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` -if (rc == RCL_RET_OK) { - printf("Error destroying node\n"); -} -``` +- Create a node with custom options: -## Node options + Node configuration will also be applied to its future elements (Publishers, subscribers, services, ...). + // TODO: explain possible options and their meaning -```C -// TODO: explain allocator and support -rcl_allocator_t allocator = rcl_get_default_allocator(); -rclc_support_t support; + The API used to customize the node options differs between ROS2 distributions: + + Foxy: The `rcl_node_options_t` is used to configure the node + + ```C + // Initialize allocator and support objects + ... + + // Create node object + rcl_node_t node; + const char * node_name = "test_node"; + + // Node namespace (Can remain empty "") + const char * namespace = "test_namespace"; + + // Get default node options and modify them + rcl_node_options_t node_ops = rcl_node_get_default_options(); + + // Set node ROS domain ID to 10 + node_ops.domain_id = (size_t)(10); + + // Init node with custom options + rc = rclc_node_init_with_options(&node, node_name, namespace, &support, &node_ops); -// Node object -rcl_node_t node; -const char * node_name = "test_node"; -const char * namespace = "test_namespace"; + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` -// TODO: explain options -rcl_node_options_t node_options = rcl_node_get_default_options(); + Galactic: In this case, the node options are configured on the `rclc_support_t` object with a custom API -rc = rclc_node_init_default(&node, node_name, namespace, &support, &node_options); -if (rc != RCL_RET_OK) { - printf("Error creating node\n"); - return -1; -} + ```C + // Initialize micro-ROS allocator + rcl_allocator_t allocator = rcl_get_default_allocator(); + + // Initialize and modify options (Set DOMAIN ID to 10) + rcl_init_options_t init_options = rcl_get_zero_initialized_init_options(); + rcl_init_options_init(&init_options, allocator); + rcl_init_options_set_domain_id(&init_options, 10); + + // Initialize rclc support object with custom options + rclc_support_t support; + rclc_support_init_with_options(&support, 0, NULL, &init_options, &allocator); + + // Create node object + rcl_node_t node; + const char * node_name = "test_node"; + + // Node namespace (Can remain empty "") + const char * namespace = "test_namespace"; + + // Init node with configured support object + rclc_node_init_default(&node, node_name, namespace, &support); + + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` + +### Cleaning Up + +To destroy a initialized node all entities owned by the node (Publishers, subscribers, services, ...) needs to be destroyed before the node itself: + +```C +// Destroy created entities +... + +// Destroy a node +rcl_node_fini(&node); +``` -``` \ No newline at end of file +This will delete any automatically created infrastructure on the agent (if possible) and deallocate used memory on the client side. diff --git a/_docs/tutorials/programming_rcl_rclc/overview/index.md b/_docs/tutorials/programming_rcl_rclc/overview/index.md index ac892992..5bbb81e9 100644 --- a/_docs/tutorials/programming_rcl_rclc/overview/index.md +++ b/_docs/tutorials/programming_rcl_rclc/overview/index.md @@ -13,5 +13,6 @@ In this tutorials, you'll learn the basics of the micro-ROS C API. The major con * [**Publishers and Subscriptions**](../pub_sub/) * [**Services**](../service/) * [**Parameters**](../parameters/) +* [**Executor and timers**](../executor/) * [**QoS**](../qos/) * [**micro-ROS Utils**](../micro-ROS/) diff --git a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md index b676cf12..6d21de42 100644 --- a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md +++ b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md @@ -5,7 +5,7 @@ permalink: /docs/tutorials/programming_rcl_rclc/parameters/ ROS 2 parameter allow the user to create variables on a node and manipulate/read them with different ROS2 commands. Further information about ROS 2 parameters can be found [here](https://docs.ros.org/en/galactic/Tutorials/Parameters/Understanding-ROS2-Parameters.html) -Ready to use code related to this tutorial can be found in [`micro-ROS-demos/rclc/parameter_server`](https://github.com/micro-ROS/micro-ROS-demos/blob/galactic/rclc/parameter_server/main.c) folder. Fragments of code from this example is used on this tutorial. +Ready to use code related to this tutorial can be found in [`rclc/rclc_examples/src/`](https://github.com/ros2/rclc/blob/master/rclc_examples/src/example_parameter_server.c) folder. Fragments of code from this example is used on this tutorial. Note: micro-ROS parameter server is only supported on ROS2 galactic distribution @@ -169,14 +169,13 @@ rc = rclc_parameter_set_double(¶m_server, parameter_name, param_value); rc = rclc_parameter_get_double(¶m_server, parameter_name, ¶m_value); ``` -## Destroy the parameter server +## Cleaning up + +To destroy an initialized parameter server: ```C // Delete parameter server -rcl_ret_t rc = rclc_parameter_server_fini(¶m_server, &node); +rclc_parameter_server_fini(¶m_server, &node); +``` -if (rc == RCL_RET_OK) { - ... // Handle error - return -1; -} -``` \ No newline at end of file +This will delete any automatically created infrastructure on the agent (if possible) and deallocate used memory on the client side. \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md index 236a433c..8fff8d2f 100644 --- a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md +++ b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md @@ -3,7 +3,7 @@ title: Publishers and Subscriptions permalink: /docs/tutorials/programming_rcl_rclc/pub_sub/ --- -ROS 2 publishers and subscribers are the basic communication mechanism between nodes using topics. Further information about ROS 2 publish–subscribe pattern can be found [here](https://docs.ros.org/en/foxy/Tutorials/Topics/Understanding-ROS2-Topics.html) +ROS 2 publishers and subscribers are the basic communication mechanism between nodes using topics. Further information about ROS 2 publish–subscribe pattern can be found [here](https://docs.ros.org/en/foxy/Tutorials/Topics/Understanding-ROS2-Topics.html). Ready to use code related to this tutorial can be found in [`micro-ROS-demos/rclc/int32_publisher`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_publisher/main.c) and [`micro-ROS-demos/rclc/int32_subscriber`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_subscriber/main.c) folders. Fragments of code from this examples are used on this tutorial. @@ -110,24 +110,6 @@ if (rc != RCL_RET_OK) { Note: `rcl_publish` is thread safe and can be called from multiple threads. -## Destroy a publisher - -Deallocates memory -After finishing the publisher, the node will no longer be advertising that it is publishing -on this topic (assuming this is the only publisher on this topic). - -```C -// Destroy publisher -rcl_ret_t rc = rcl_publisher_fini(&publisher, &node); - -if (rc == RCL_RET_OK) { - ... // Handle error - return -1; -} -``` - -Note: Publishers needs to be destroyed before its containing node - ## Subscription ### Initialization @@ -221,16 +203,16 @@ if (RCL_RET_OK != rc) { } ``` -### Destroy a subscriber -Destroy subscriber (Publisher needs to be destroyed manually before the node) -Destroys any automatically created infrastructure and deallocates memory. +### Cleaning Up -```C -// Destroy -rcl_ret_t rc = rcl_publisher_fini(&subscriber, &node); +To destroy an initialized publisher or subscriber: -if (RCL_RET_OK != rc) { - ... // Handle error - return -1; -} +```C +// Destroy publisher and subscriber +rcl_publisher_fini(&publisher, &node); +rcl_subscription_fini(&subscriber, &node); ``` + +After finishing the publisher/subscriber, the node will no longer be advertising that it is publishing/listening on the topic. + +This will delete any automatically created infrastructure on the agent (if possible) and deallocate used memory on the client side. \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/service/services.md b/_docs/tutorials/programming_rcl_rclc/service/services.md index de7638cc..252d5d73 100644 --- a/_docs/tutorials/programming_rcl_rclc/service/services.md +++ b/_docs/tutorials/programming_rcl_rclc/service/services.md @@ -278,3 +278,15 @@ rcl_send_request(&client, &request_msg, &sequence_number); // Spin the executor to get the response rclc_executor_spin(&executor); ``` + +### Cleaning Up + +To destroy an initialized service or client: + +```C +// Destroy service server and client +rcl_service_fini(&service, &node); +rcl_client_fini(&client, &node); +``` + +This will delete any automatically created infrastructure on the agent (if possible) and deallocate used memory on the client side. From 50562ef67795ea95589ffbef22cea409fe196025 Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Fri, 6 Aug 2021 13:33:28 +0200 Subject: [PATCH 04/14] Update QoS and style --- .../programming_rcl_rclc/executor/executor.md | 4 +- .../micro-ROS/micro-ROS.md | 27 ++------ .../programming_rcl_rclc/node/node.md | 22 +++---- .../programming_rcl_rclc/overview/index.md | 2 +- .../programming_rcl_rclc/pub_sub/pub_sub.md | 64 ++++++++++--------- .../tutorials/programming_rcl_rclc/qos/QoS.md | 49 ++++++++++---- .../programming_rcl_rclc/service/services.md | 54 ++++------------ 7 files changed, 100 insertions(+), 122 deletions(-) diff --git a/_docs/tutorials/programming_rcl_rclc/executor/executor.md b/_docs/tutorials/programming_rcl_rclc/executor/executor.md index c620c44b..bdffc10a 100644 --- a/_docs/tutorials/programming_rcl_rclc/executor/executor.md +++ b/_docs/tutorials/programming_rcl_rclc/executor/executor.md @@ -1,6 +1,6 @@ --- title: Executor and timers -permalink: /docs/tutorials/programming_rcl_rclc/micro-ROS/ +permalink: /docs/tutorials/programming_rcl_rclc/executor/ --- -// TODO: Use existing tutorial if possible \ No newline at end of file +// TODO: Use existing parts of previous tutorial if possible \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md index ed766f5d..84063776 100644 --- a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md +++ b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md @@ -3,44 +3,31 @@ title: micro-ROS utilities permalink: /docs/tutorials/programming_rcl_rclc/micro-ROS/ --- -## micro-ROS features +// TODO - -- Custom transport -rmw_ret_t rmw_uros_set_custom_transport( - bool framing, - void * args, - open_custom_func open_cb, - close_custom_func close_cb, - write_custom_func write_cb, - read_custom_func read_cb); - - -- Time sync +## Time sync ```C - bool rmw_uros_epoch_synchronized(); int64_t rmw_uros_epoch_millis(); int64_t rmw_uros_epoch_nanos(); rmw_ret_t rmw_uros_sync_session(const int timeout_ms); - ``` -- Ping agent +## Ping agent +```C rmw_ret_t rmw_uros_ping_agent(const int timeout_ms, const uint8_t attempts); - +``` - Init options ?? - Discovery ?? - Continous serialization ?? +-```C void rmw_uros_set_continous_serialization_callbacks( rmw_publisher_t * publisher, rmw_uros_continous_serialization_size size_cb, rmw_uros_continous_serialization serialization_cb); - - -// Add publisher / service / client timeout here? \ No newline at end of file +``` \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/node/node.md b/_docs/tutorials/programming_rcl_rclc/node/node.md index 6593707f..93c1dba4 100644 --- a/_docs/tutorials/programming_rcl_rclc/node/node.md +++ b/_docs/tutorials/programming_rcl_rclc/node/node.md @@ -3,9 +3,9 @@ title: Nodes permalink: /docs/tutorials/programming_rcl_rclc/node/ --- -ROS 2 nodes are the ground element on ROS2 ecosystem. They will contain communicate between each other using publishers, subscriptions, services, ... .Further information about ROS 2 nodes can be found [here](https://docs.ros.org/en/galactic/Tutorials/Understanding-ROS2-Nodes.html) +ROS 2 nodes are the main participants on ROS2 ecosystem. They will communicate between each other using publishers, subscriptions, services, ... Further information about ROS 2 nodes can be found [here](https://docs.ros.org/en/galactic/Tutorials/Understanding-ROS2-Nodes.html) -// TODO: explain general micro-ROS initialization (allocator and support) +// TODO: explain general micro-ROS initialization (allocator and support). Where? ## Initialization - Create a node with default configuration: @@ -33,12 +33,9 @@ ROS 2 nodes are the ground element on ROS2 ecosystem. They will contain communic ``` - Create a node with custom options: + // TODO: explain possible options - Node configuration will also be applied to its future elements (Publishers, subscribers, services, ...). - - // TODO: explain possible options and their meaning - - The API used to customize the node options differs between ROS2 distributions: + The configuration of the node will also be applied to its future elements (Publishers, subscribers, services, ...).The API used to customize the node options differs between ROS2 distributions: Foxy: The `rcl_node_options_t` is used to configure the node @@ -67,7 +64,7 @@ ROS 2 nodes are the ground element on ROS2 ecosystem. They will contain communic return -1; } ``` - + Galactic: In this case, the node options are configured on the `rclc_support_t` object with a custom API ```C @@ -101,14 +98,15 @@ ROS 2 nodes are the ground element on ROS2 ecosystem. They will contain communic ### Cleaning Up -To destroy a initialized node all entities owned by the node (Publishers, subscribers, services, ...) needs to be destroyed before the node itself: +To destroy a initialized node all entities owned by the node (Publishers, subscribers, services, ...) have to be destroyed before the node itself: ```C -// Destroy created entities +// Destroy created entities (Example) +rcl_publisher_fini(&publisher, &node); ... -// Destroy a node +// Destroy the node rcl_node_fini(&node); ``` -This will delete any automatically created infrastructure on the agent (if possible) and deallocate used memory on the client side. +This will delete the node from ROS2 graph, including any generated infrastructure on the agent (if possible) and used memory on the client. diff --git a/_docs/tutorials/programming_rcl_rclc/overview/index.md b/_docs/tutorials/programming_rcl_rclc/overview/index.md index 5bbb81e9..a48da0dd 100644 --- a/_docs/tutorials/programming_rcl_rclc/overview/index.md +++ b/_docs/tutorials/programming_rcl_rclc/overview/index.md @@ -9,7 +9,7 @@ redirect_from: In this tutorials, you'll learn the basics of the micro-ROS C API. The major concepts (publishers, subscriptions, services,timers, ...) are identical with ROS 2. They even rely on the *same* implementation, as the micro-ROS C API is based on the ROS 2 client support library (rcl), enriched with a set of convenience functions by the package [rclc](https://github.com/ros2/rclc/). That is, rclc does not add a new layer of types on top of rcl (like rclcpp and rclpy do) but only provides functions that ease the programming with the rcl types. New types are introduced only for concepts that are missing in rcl, such as the concept of an executor. -* [**Node**](../node/) +* [**Nodes**](../node/) * [**Publishers and Subscriptions**](../pub_sub/) * [**Services**](../service/) * [**Parameters**](../parameters/) diff --git a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md index 8fff8d2f..b4845eaa 100644 --- a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md +++ b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md @@ -5,7 +5,9 @@ permalink: /docs/tutorials/programming_rcl_rclc/pub_sub/ ROS 2 publishers and subscribers are the basic communication mechanism between nodes using topics. Further information about ROS 2 publish–subscribe pattern can be found [here](https://docs.ros.org/en/foxy/Tutorials/Topics/Understanding-ROS2-Topics.html). -Ready to use code related to this tutorial can be found in [`micro-ROS-demos/rclc/int32_publisher`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_publisher/main.c) and [`micro-ROS-demos/rclc/int32_subscriber`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_subscriber/main.c) folders. Fragments of code from this examples are used on this tutorial. +Ready to use code related to this concepts can be found in [`micro-ROS-demos/rclc/int32_publisher`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_publisher/main.c) and [`micro-ROS-demos/rclc/int32_subscriber`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_subscriber/main.c) folders. Fragments of code from this examples are used on this tutorial. + +// TODO: add index? ## Publisher @@ -13,9 +15,7 @@ Ready to use code related to this tutorial can be found in [`micro-ROS-demos/rcl Starting from a code where RCL is initialized and a micro-ROS node is created, there are tree ways to initialize a publisher depending on the desired quality-of-service configuration: -// TODO: explain and link diferences between each approach on QoS section - -- Reliable: +- Reliable (default): ```C // Publisher object rcl_publisher_t publisher; @@ -27,28 +27,12 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t // Creates a reliable rcl publisher rcl_ret_t rc = rclc_publisher_init_default(&publisher, &node, &type_support, &topic_name); - if (RCL_RET_OK != rc) { - ... // Handle error - return -1; - } - ``` - - // TODO: move to micro-ROS features section? - Reliable publishers will wait for confirmation for each published message, which leads to blocking `rcl_publish` calls, `rmw-microxrcedds` offers an API to configure the acknowledgement timeout for each publisher: - - ```C - // Set confirmation timeout in milliseconds - int ack_timeout = 1000; - rc = rmw_uros_set_publisher_session_timeout(&publisher, ack_timeout); - if (RCL_RET_OK != rc) { ... // Handle error return -1; } ``` - The default value for all publishers is configured at compilation time by the cmake variable `RMW_UXRCE_PUBLISH_RELIABLE_TIMEOUT`. - - Best effort: ```C // Publisher object @@ -66,7 +50,7 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t return -1; } ``` - + - Custom QoS: ```C @@ -88,11 +72,13 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t return -1; } ``` + + For a detail on the avaliable QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). ## Publish a message -// TODO: explain message memory allocation and link to tutorial -// TODO: explain periodic publication and link to timers +To publish messages to the topic: + ```C // Int32 message object std_msgs__msg__Int32 msg; @@ -102,12 +88,15 @@ msg.data = 0; // Publish message rcl_ret_t rc = rcl_publish(&publisher, &msg, NULL); + if (rc != RCL_RET_OK) { ... // Handle error return -1; } ``` +For periodic publications, `rcl_publish` can be placed inside a timer callback. Check the [Executor and timers](../executor/) section for details. + Note: `rcl_publish` is thread safe and can be called from multiple threads. ## Subscription @@ -153,7 +142,7 @@ The suscriptor initialization is almost identical to the publisher one: } ``` -- Add QoS API +- Custom QoS: ```C // Subscription object @@ -175,8 +164,13 @@ The suscriptor initialization is almost identical to the publisher one: } ``` +For a detail on the avaliable QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). + ### Callbacks +The executor is responsible to call the configured callback when a message is published. +The function will have the message as its only argument, containing the values sended by the publisher: +// TODO: explain function prototype? ```C void subscription_callback(const void * msgin) { @@ -188,10 +182,11 @@ void subscription_callback(const void * msgin) } ``` -Once the subscriber and the executor are initialized, the subscriber callback must be added to the executor to receive incoming publications once the executor is spinning: +Once the subscriber and the executor are initialized, the subscriber callback must be added to the executor to receive incoming publications once its spinning: + ```C -// Message object to save publication data +// Message object to receive publisher data std_msgs__msg__Int32 msg; // Add subscription to the executor @@ -200,19 +195,26 @@ rcl_ret_t rc = rclc_executor_add_subscription(&executor, &subscriber, &msg, &sub if (RCL_RET_OK != rc) { ... // Handle error return -1; -} + +// Spin executor to receive messages +rclc_executor_spin(&executor); ``` -### Cleaning Up +## Message initialization +Before publishing or receiving a message, it may be neccesary to initialize its memory for types with strings or sequences. +Check the [Handling messages memory in micro-ROS](../../advanced/handling_type_memory/) section for details. +## Cleaning Up + +After finishing the publisher/subscriber, the node will no longer be advertising that it is publishing/listening on the topic. To destroy an initialized publisher or subscriber: ```C -// Destroy publisher and subscriber +// Destroy publisher rcl_publisher_fini(&publisher, &node); + +// Destroy subscriber rcl_subscription_fini(&subscriber, &node); ``` -After finishing the publisher/subscriber, the node will no longer be advertising that it is publishing/listening on the topic. - This will delete any automatically created infrastructure on the agent (if possible) and deallocate used memory on the client side. \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md index a30b33a4..58061091 100644 --- a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md +++ b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md @@ -3,10 +3,43 @@ title: Quality of service permalink: /docs/tutorials/programming_rcl_rclc/qos/ --- -## QoS +// Add benchmark results for Throughput and RTT to compare both modes? +// Explain custom QoS options + +## Reliable QoS -```C +Reliable communication implies a confirmation for each message sended. This mode can detect errors in the communication process at the cost of increasing the message latency and the resources usage. + +This message confirmation proccess can increase blocking time on `rcl_publish` or executor spin calls as reliable publishers, services and clients will wait for acknowledgement for each sended message. `rmw-microxrcedds` offers an API to individually configure the acknowledgement timeout on them: + + ```C + // Confirmation timeout in milliseconds + int ack_timeout = 1000; + + // Set reliable publisher timeout + rc = rmw_uros_set_publisher_session_timeout(&publisher, ack_timeout); + + // Set reliable service server timeout + rc = rmw_uros_set_service_session_timeout(&service, ack_timeout); + + // Set reliable service client timeout + rc = rmw_uros_set_client_session_timeout(&client, ack_timeout); + + if (RCL_RET_OK != rc) { + ... // Handle error + return -1; + } + ``` + + The default value for all publishers is configured at compilation time by the cmake variable `RMW_UXRCE_PUBLISH_RELIABLE_TIMEOUT`. +## Best Effort + +In best effort mode no acknowledgement is needed, the messages sended are expected to be received. This method improves publication throughput and reduces resources usage but is vulnerable to communication errors. + +## Custom QoS configuration + +```C /// ROS MiddleWare quality of service profile. typedef struct RMW_PUBLIC_TYPE rmw_qos_profile_t { @@ -27,19 +60,7 @@ typedef struct RMW_PUBLIC_TYPE rmw_qos_profile_t struct rmw_time_t liveliness_lease_duration; /// If true, any ROS specific namespacing conventions will be circumvented. - /** - * In the case of DDS and topics, for example, this means the typical - * ROS specific prefix of `rt` would not be applied as described here: - * - * http://design.ros2.org/articles/topic_and_service_names.html#ros-specific-namespace-prefix - * - * This might be useful when trying to directly connect a native DDS topic - * with a ROS 2 topic. - */ bool avoid_ros_namespace_conventions; } rmw_qos_profile_t; ``` - - -// TODO: explain difference between reliable and best effort \ No newline at end of file diff --git a/_docs/tutorials/programming_rcl_rclc/service/services.md b/_docs/tutorials/programming_rcl_rclc/service/services.md index 252d5d73..3d6451cd 100644 --- a/_docs/tutorials/programming_rcl_rclc/service/services.md +++ b/_docs/tutorials/programming_rcl_rclc/service/services.md @@ -5,15 +5,13 @@ permalink: /docs/tutorials/programming_rcl_rclc/service/ ROS 2 services are another communication mechanism between nodes. Services implement a client-server paradigm based on ROS 2 messages and types. Further information about ROS 2 services can be found [here](https://index.ros.org/doc/ros2/Tutorials/Services/Understanding-ROS2-Services/) -Ready to use code related to this tutorial can be found in [`micro-ROS-demos/rclc/addtwoints_server`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_server/main.c) and [`micro-ROS-demos/rclc/addtwoints_client`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_client/main.c) folders. Fragments of code from this examples are used on this tutorial. +Ready to use code related to this concepts can be found in [`micro-ROS-demos/rclc/addtwoints_server`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_server/main.c) and [`micro-ROS-demos/rclc/addtwoints_client`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_client/main.c) folders. Fragments of code from this examples are used on this tutorial. ## Service server ### Initialization Starting from a code where RCL is initialized and a micro-ROS node is created, there are tree ways to initialize a service server: -// TODO: explain and link diferences between each approach on QoS section - - Reliable (default): ```C @@ -33,21 +31,6 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t } ``` - A reliable service server will wait for confirmation for each response sended, which can increase the blocking time of the executor spins, `rmw-microxrcedds` offers an API to configure the acknowledgement timeout for each service: - - ```C - // Set confirmation timeout in milliseconds - int ack_timeout = 1000; - rc = rmw_uros_set_service_session_timeout(&service, ack_timeout); - - if (RCL_RET_OK != rc) { - ... // Handle error - return -1; - } - ``` - - The default value for all clients is configured at compilation time by the cmake variable `RMW_UXRCE_PUBLISH_RELIABLE_TIMEOUT`. - - Best effort: ```C @@ -88,10 +71,11 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t return -1; } ``` - + +For a detail on the avaliable QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). + ### Callback -// TODO: add and explain function prototype? Once a request arrives, the executor will call the configured callback with the request and response messages as arguments. The request message contains the values sended by the client, the response_msg should be modified here as it will be delivered after the callback returns. @@ -130,17 +114,13 @@ example_interfaces__srv__AddTwoInts_Request request_msg; // Add server callback to the executor rc = rclc_executor_add_service(&executor, &service, &request_msg, &response_msg, service_callback); -// Spin executor to receive requests -rclc_executor_spin(&executor); - if (rc != RCL_RET_OK) { ... // Handle error return -1; } -// TODO ?? -// rclc_executor_add_service_with_context -// rclc_executor_add_service_with_request_id +// Spin executor to receive requests +rclc_executor_spin(&executor); ``` ## Service Client @@ -167,21 +147,6 @@ The service client initialization is almost identical to the server one: } ``` - A reliable service client will wait for confirmation for each request sended, which can increase the blocking time of the executor spins, `rmw-microxrcedds` offers an API to configure the acknowledgement timeout for each client: - - ```C - // Set confirmation timeout in milliseconds - int ack_timeout = 1000; - rc = rmw_uros_set_client_session_timeout(&client, ack_timeout); - - if (RCL_RET_OK != rc) { - ... // Handle error - return -1; - } - ``` - - The default value for all clients is configured at compilation time by the cmake variable `RMW_UXRCE_PUBLISH_RELIABLE_TIMEOUT`. - - Best effort: ```C @@ -265,6 +230,7 @@ Following the example on `AddTwoInts.srv`: example_interfaces__srv__AddTwoInts_Request request_msg; // Initialize request message memory and set its values +// TODO: use custom API? example_interfaces__srv__AddTwoInts_Request__init(&request_msg); request_msg.a = 24; request_msg.b = 42; @@ -279,7 +245,11 @@ rcl_send_request(&client, &request_msg, &sequence_number); rclc_executor_spin(&executor); ``` -### Cleaning Up +## Message initialization +Before sending or receiving a message, it may be neccesary to initialize its memory for types with strings or sequences. +Check the [Handling messages memory in micro-ROS](../../advanced/handling_type_memory/) section for details. + +## Cleaning Up To destroy an initialized service or client: From 2bce003c1d3032b24674ec81589b469b6bf9eee8 Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Fri, 6 Aug 2021 13:48:41 +0200 Subject: [PATCH 05/14] Fix typos --- .../programming_rcl_rclc/parameters/parameters.md | 4 ++-- .../programming_rcl_rclc/pub_sub/pub_sub.md | 10 +++++----- _docs/tutorials/programming_rcl_rclc/qos/QoS.md | 6 +++--- .../programming_rcl_rclc/service/services.md | 14 +++++++------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md index 6d21de42..421ba918 100644 --- a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md +++ b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md @@ -45,7 +45,7 @@ Note: micro-ROS parameter server is only supported on ROS2 galactic distribution - Memory and executor requirements: The variable `RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER` defines the RCLC executor handles required for a parameter server. - This needs to be taken into account when initializing the executor and on the colcon memory configuration of the `rmw-microxredds` package, which will need atleast 4 services and 1 publisher: + This needs to be taken into account when initializing the executor and on the colcon memory configuration of the `rmw-microxredds` package, which will need at least 4 services and 1 publisher: ```C # colcon.meta example with minimum memory requirements to use parameter server @@ -72,7 +72,7 @@ Note: micro-ROS parameter server is only supported on ROS2 galactic distribution ## Callback -When adding the paramater server to the executor, a callback can be configured. +When adding the parameter server to the executor, a callback can be configured. This callback will be executed after a parameter value is modified. A pointer to the changed parameter is passed as first and only argument. Example: diff --git a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md index b4845eaa..09cf965c 100644 --- a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md +++ b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md @@ -73,7 +73,7 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t } ``` - For a detail on the avaliable QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). + For a detail on the available QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). ## Publish a message @@ -114,7 +114,7 @@ The suscriptor initialization is almost identical to the publisher one: // Get message type support const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); - // Initialize a realiable subscriber + // Initialize a reliable subscriber rcl_ret_t rc = rclc_subscription_init_default(&subscriber, &node, &type_support, &topic_name); if (RCL_RET_OK != rc) { @@ -164,11 +164,11 @@ The suscriptor initialization is almost identical to the publisher one: } ``` -For a detail on the avaliable QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). +For a detail on the available QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). ### Callbacks The executor is responsible to call the configured callback when a message is published. -The function will have the message as its only argument, containing the values sended by the publisher: +The function will have the message as its only argument, containing the values sent by the publisher: // TODO: explain function prototype? ```C @@ -201,7 +201,7 @@ rclc_executor_spin(&executor); ``` ## Message initialization -Before publishing or receiving a message, it may be neccesary to initialize its memory for types with strings or sequences. +Before publishing or receiving a message, it may be necessary to initialize its memory for types with strings or sequences. Check the [Handling messages memory in micro-ROS](../../advanced/handling_type_memory/) section for details. ## Cleaning Up diff --git a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md index 58061091..c7d44085 100644 --- a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md +++ b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md @@ -8,9 +8,9 @@ permalink: /docs/tutorials/programming_rcl_rclc/qos/ ## Reliable QoS -Reliable communication implies a confirmation for each message sended. This mode can detect errors in the communication process at the cost of increasing the message latency and the resources usage. +Reliable communication implies a confirmation for each message sent. This mode can detect errors in the communication process at the cost of increasing the message latency and the resources usage. -This message confirmation proccess can increase blocking time on `rcl_publish` or executor spin calls as reliable publishers, services and clients will wait for acknowledgement for each sended message. `rmw-microxrcedds` offers an API to individually configure the acknowledgement timeout on them: +This message confirmation proccess can increase blocking time on `rcl_publish` or executor spin calls as reliable publishers, services and clients will wait for acknowledgement for each sent message. `rmw-microxrcedds` offers an API to individually configure the acknowledgement timeout on them: ```C // Confirmation timeout in milliseconds @@ -35,7 +35,7 @@ This message confirmation proccess can increase blocking time on `rcl_publish` o ## Best Effort -In best effort mode no acknowledgement is needed, the messages sended are expected to be received. This method improves publication throughput and reduces resources usage but is vulnerable to communication errors. +In best effort mode no acknowledgement is needed, the messages sent are expected to be received. This method improves publication throughput and reduces resources usage but is vulnerable to communication errors. ## Custom QoS configuration diff --git a/_docs/tutorials/programming_rcl_rclc/service/services.md b/_docs/tutorials/programming_rcl_rclc/service/services.md index 3d6451cd..e4b8c844 100644 --- a/_docs/tutorials/programming_rcl_rclc/service/services.md +++ b/_docs/tutorials/programming_rcl_rclc/service/services.md @@ -72,12 +72,12 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t } ``` -For a detail on the avaliable QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). +For a detail on the available QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). ### Callback Once a request arrives, the executor will call the configured callback with the request and response messages as arguments. -The request message contains the values sended by the client, the response_msg should be modified here as it will be delivered after the callback returns. +The request message contains the values sent by the client, the response_msg should be modified here as it will be delivered after the callback returns. Using `AddTwoInts.srv` type definition as an example: @@ -88,7 +88,7 @@ int64 b int64 sum ``` -The client request message will contain two integers `a` and `b`, and expectes the `sum` of them as a response: +The client request message will contain two integers `a` and `b`, and expects the `sum` of them as a response: ```C void service_callback(const void * request_msg, void * response_msg){ @@ -102,7 +102,7 @@ void service_callback(const void * request_msg, void * response_msg){ } ``` -Note that it is neccesary to cast each message to the expected type +Note that it is necessary to cast each message to the expected type Once the service and the executor are initialized, the service callback must be added to the executor in order to process incoming requests once the executor is spinning: @@ -190,9 +190,9 @@ The service client initialization is almost identical to the server one: ### Callback The executor is responsible to call the configured callback when the service response arrives. -The function will have the response message as its only argument, containing the values sended by the server. +The function will have the response message as its only argument, containing the values sent by the server. -It is neccesary to cast the response message to the expected type. Example: +It is necessary to cast the response message to the expected type. Example: ```C void client_callback(const void * response_msg){ // Cast response message to expected type @@ -246,7 +246,7 @@ rclc_executor_spin(&executor); ``` ## Message initialization -Before sending or receiving a message, it may be neccesary to initialize its memory for types with strings or sequences. +Before sending or receiving a message, it may be necessary to initialize its memory for types with strings or sequences. Check the [Handling messages memory in micro-ROS](../../advanced/handling_type_memory/) section for details. ## Cleaning Up From 39eaf5eb5b03496703c766c471848924126aad9b Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Fri, 6 Aug 2021 14:01:53 +0200 Subject: [PATCH 06/14] Update parameters with memory requirements --- .../parameters/parameters.md | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md index 421ba918..e20722ab 100644 --- a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md +++ b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md @@ -5,7 +5,7 @@ permalink: /docs/tutorials/programming_rcl_rclc/parameters/ ROS 2 parameter allow the user to create variables on a node and manipulate/read them with different ROS2 commands. Further information about ROS 2 parameters can be found [here](https://docs.ros.org/en/galactic/Tutorials/Parameters/Understanding-ROS2-Parameters.html) -Ready to use code related to this tutorial can be found in [`rclc/rclc_examples/src/`](https://github.com/ros2/rclc/blob/master/rclc_examples/src/example_parameter_server.c) folder. Fragments of code from this example is used on this tutorial. +Ready to use code related to this tutorial can be found in [`rclc/rclc_examples/src/example_parameter_server.c`](https://github.com/ros2/rclc/blob/master/rclc_examples/src/example_parameter_server.c). Fragments of code from this example is used on this tutorial. Note: micro-ROS parameter server is only supported on ROS2 galactic distribution @@ -43,32 +43,33 @@ Note: micro-ROS parameter server is only supported on ROS2 galactic distribution } ``` -- Memory and executor requirements: - The variable `RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER` defines the RCLC executor handles required for a parameter server. - This needs to be taken into account when initializing the executor and on the colcon memory configuration of the `rmw-microxredds` package, which will need at least 4 services and 1 publisher: +## Memory requirements +The parameter server uses 4 services and an optional publisher, this needs to be taken into account on the `rmw-microxredds` package memory configuration: - ```C - # colcon.meta example with minimum memory requirements to use parameter server - { - "names": { - "rmw_microxrcedds": { - "cmake-args": [ - "-DRMW_UXRCE_MAX_NODES=1", - "-DRMW_UXRCE_MAX_PUBLISHERS=1", - "-DRMW_UXRCE_MAX_SUBSCRIPTIONS=0", - "-DRMW_UXRCE_MAX_SERVICES=4", - "-DRMW_UXRCE_MAX_CLIENTS=0" - ] - } +```C +# colcon.meta example with memory requirements to use a parameter server +{ + "names": { + "rmw_microxrcedds": { + "cmake-args": [ + "-DRMW_UXRCE_MAX_NODES=1", + "-DRMW_UXRCE_MAX_PUBLISHERS=1", + "-DRMW_UXRCE_MAX_SUBSCRIPTIONS=0", + "-DRMW_UXRCE_MAX_SERVICES=4", + "-DRMW_UXRCE_MAX_CLIENTS=0" + ] } } - ``` - - ```C - // Executor init example with the minimum RCLC executor handles required - rclc_executor_t executor = rclc_executor_get_zero_initialized_executor(); - rc = rclc_executor_init(&executor, &support.context, RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER, &allocator); - ``` +} +``` + +On runtime, the variable `RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER` defines the RCLC executor handles required for a parameter server: + +```C +// Executor init example with the minimum RCLC executor handles required +rclc_executor_t executor = rclc_executor_get_zero_initialized_executor(); +rc = rclc_executor_init(&executor, &support.context, RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER, &allocator); +``` ## Callback From 5983aa4960da3ef8a99fe5adcbf3009a305a8910 Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Fri, 6 Aug 2021 14:05:30 +0200 Subject: [PATCH 07/14] Ident changes --- _docs/tutorials/programming_rcl_rclc/parameters/parameters.md | 2 +- _docs/tutorials/programming_rcl_rclc/qos/QoS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md index e20722ab..24235fce 100644 --- a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md +++ b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md @@ -25,7 +25,7 @@ Note: micro-ROS parameter server is only supported on ROS2 galactic distribution } ``` -// TODO: explain options + // TODO: explain options - Custom options: ```C // Parameter server object diff --git a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md index c7d44085..fab556c6 100644 --- a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md +++ b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md @@ -3,7 +3,7 @@ title: Quality of service permalink: /docs/tutorials/programming_rcl_rclc/qos/ --- -// Add benchmark results for Throughput and RTT to compare both modes? +// Add benchmark results for Throughput and RTT to compare both modes? // Explain custom QoS options ## Reliable QoS From 76f60d19c12b08272eb006cbe22abb763b51009f Mon Sep 17 00:00:00 2001 From: Pablo Garrido Date: Mon, 9 Aug 2021 09:44:33 +0200 Subject: [PATCH 08/14] Minor updates --- .../micro-ROS/micro-ROS.md | 13 +- .../programming_rcl_rclc/node/node.md | 37 +++-- .../programming_rcl_rclc/overview/index.md | 3 +- .../parameters/parameters.md | 48 +++--- .../programming_rcl_rclc/pub_sub/pub_sub.md | 113 ++++++++------ .../tutorials/programming_rcl_rclc/qos/QoS.md | 22 +-- .../programming_rcl_rclc/service/services.md | 143 +++++++++++------- 7 files changed, 231 insertions(+), 148 deletions(-) diff --git a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md index 84063776..406765da 100644 --- a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md +++ b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md @@ -5,18 +5,21 @@ permalink: /docs/tutorials/programming_rcl_rclc/micro-ROS/ // TODO -## Time sync +- [Time sync](#time-sync) +- [Ping agent](#ping-agent) -```C +## Time sync + +```c bool rmw_uros_epoch_synchronized(); int64_t rmw_uros_epoch_millis(); int64_t rmw_uros_epoch_nanos(); rmw_ret_t rmw_uros_sync_session(const int timeout_ms); ``` -## Ping agent +## Ping agent -```C +```c rmw_ret_t rmw_uros_ping_agent(const int timeout_ms, const uint8_t attempts); ``` @@ -25,7 +28,7 @@ rmw_ret_t rmw_uros_ping_agent(const int timeout_ms, const uint8_t attempts); - Discovery ?? - Continous serialization ?? --```C +-```c void rmw_uros_set_continous_serialization_callbacks( rmw_publisher_t * publisher, rmw_uros_continous_serialization_size size_cb, diff --git a/_docs/tutorials/programming_rcl_rclc/node/node.md b/_docs/tutorials/programming_rcl_rclc/node/node.md index 93c1dba4..6593e968 100644 --- a/_docs/tutorials/programming_rcl_rclc/node/node.md +++ b/_docs/tutorials/programming_rcl_rclc/node/node.md @@ -3,13 +3,17 @@ title: Nodes permalink: /docs/tutorials/programming_rcl_rclc/node/ --- -ROS 2 nodes are the main participants on ROS2 ecosystem. They will communicate between each other using publishers, subscriptions, services, ... Further information about ROS 2 nodes can be found [here](https://docs.ros.org/en/galactic/Tutorials/Understanding-ROS2-Nodes.html) +ROS 2 nodes are the main participants on ROS 2 ecosystem. They will communicate between each other using publishers, subscriptions, services, etc. Further information about ROS 2 nodes can be found [here](https://docs.ros.org/en/galactic/Tutorials/Understanding-ROS2-Nodes.html) // TODO: explain general micro-ROS initialization (allocator and support). Where? -## Initialization + +- [Initialization](#initialization) + - [Cleaning Up](#cleaning-up) + +## Initialization - Create a node with default configuration: - ```C + ```c // Initialize micro-ROS allocator rcl_allocator_t allocator = rcl_get_default_allocator(); @@ -33,13 +37,14 @@ ROS 2 nodes are the main participants on ROS2 ecosystem. They will communicate b ``` - Create a node with custom options: + // TODO: explain possible options The configuration of the node will also be applied to its future elements (Publishers, subscribers, services, ...).The API used to customize the node options differs between ROS2 distributions: Foxy: The `rcl_node_options_t` is used to configure the node - ```C + ```c // Initialize allocator and support objects ... @@ -51,37 +56,37 @@ ROS 2 nodes are the main participants on ROS2 ecosystem. They will communicate b const char * namespace = "test_namespace"; // Get default node options and modify them - rcl_node_options_t node_ops = rcl_node_get_default_options(); + rcl_node_options_t node_ops = rcl_node_get_default_options(); // Set node ROS domain ID to 10 - node_ops.domain_id = (size_t)(10); + node_ops.domain_id = (size_t)(10); // Init node with custom options - rc = rclc_node_init_with_options(&node, node_name, namespace, &support, &node_ops); + rc = rclc_node_init_with_options(&node, node_name, namespace, &support, &node_ops); if (rc != RCL_RET_OK) { ... // Handle error return -1; } ``` - + Galactic: In this case, the node options are configured on the `rclc_support_t` object with a custom API - ```C + ```c // Initialize micro-ROS allocator rcl_allocator_t allocator = rcl_get_default_allocator(); // Initialize and modify options (Set DOMAIN ID to 10) - rcl_init_options_t init_options = rcl_get_zero_initialized_init_options(); - rcl_init_options_init(&init_options, allocator); - rcl_init_options_set_domain_id(&init_options, 10); + rcl_init_options_t init_options = rcl_get_zero_initialized_init_options(); + rcl_init_options_init(&init_options, allocator); + rcl_init_options_set_domain_id(&init_options, 10); // Initialize rclc support object with custom options rclc_support_t support; - rclc_support_init_with_options(&support, 0, NULL, &init_options, &allocator); + rclc_support_init_with_options(&support, 0, NULL, &init_options, &allocator); // Create node object - rcl_node_t node; + rcl_node_t node; const char * node_name = "test_node"; // Node namespace (Can remain empty "") @@ -96,11 +101,11 @@ ROS 2 nodes are the main participants on ROS2 ecosystem. They will communicate b } ``` -### Cleaning Up +### Cleaning Up To destroy a initialized node all entities owned by the node (Publishers, subscribers, services, ...) have to be destroyed before the node itself: -```C +```c // Destroy created entities (Example) rcl_publisher_fini(&publisher, &node); ... diff --git a/_docs/tutorials/programming_rcl_rclc/overview/index.md b/_docs/tutorials/programming_rcl_rclc/overview/index.md index a48da0dd..2371b80c 100644 --- a/_docs/tutorials/programming_rcl_rclc/overview/index.md +++ b/_docs/tutorials/programming_rcl_rclc/overview/index.md @@ -6,8 +6,9 @@ redirect_from: --- +In this section, you'll learn the basics of the micro-ROS C API: **rclc**. -In this tutorials, you'll learn the basics of the micro-ROS C API. The major concepts (publishers, subscriptions, services,timers, ...) are identical with ROS 2. They even rely on the *same* implementation, as the micro-ROS C API is based on the ROS 2 client support library (rcl), enriched with a set of convenience functions by the package [rclc](https://github.com/ros2/rclc/). That is, rclc does not add a new layer of types on top of rcl (like rclcpp and rclpy do) but only provides functions that ease the programming with the rcl types. New types are introduced only for concepts that are missing in rcl, such as the concept of an executor. +The major concepts (publishers, subscriptions, services, timers, ...) are identical with ROS 2. They even rely on the *same* implementation, as the micro-ROS C API is based on the ROS 2 client support library (rcl), enriched with a set of convenience functions by the package [rclc](https://github.com/ros2/rclc/). That is, rclc does not add a new layer of types on top of rcl (like rclcpp and rclpy do) but only provides functions that ease the programming with the rcl types. New types are introduced only for concepts that are missing in rcl, such as the concept of an executor. * [**Nodes**](../node/) * [**Publishers and Subscriptions**](../pub_sub/) diff --git a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md index 24235fce..a371afa7 100644 --- a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md +++ b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md @@ -9,10 +9,16 @@ Ready to use code related to this tutorial can be found in [`rclc/rclc_examples/ Note: micro-ROS parameter server is only supported on ROS2 galactic distribution -## Initialization +- [Initialization](#initialization) +- [Memory requirements](#memory-requirements) +- [Callback](#callback) +- [Add a parameter](#add-a-parameter) +- [Cleaning up](#cleaning-up) + +## Initialization - Default initialization: - ```C + ```c // Parameter server object rclc_parameter_server_t param_server; @@ -27,12 +33,14 @@ Note: micro-ROS parameter server is only supported on ROS2 galactic distribution // TODO: explain options - Custom options: - ```C + ```c // Parameter server object rclc_parameter_server_t param_server; // Define parameter server options - const rclc_parameter_options_t options = { .notify_changed_over_dds = true, .max_params = 4 }; + const rclc_parameter_options_t options = { + .notify_changed_over_dds = true, + .max_params = 4 }; // Initialize parameter server with configured options rcl_ret_t rc = rclc_parameter_server_init_with_option(¶m_server, &node, &options); @@ -43,10 +51,10 @@ Note: micro-ROS parameter server is only supported on ROS2 galactic distribution } ``` -## Memory requirements +## Memory requirements The parameter server uses 4 services and an optional publisher, this needs to be taken into account on the `rmw-microxredds` package memory configuration: -```C +```json # colcon.meta example with memory requirements to use a parameter server { "names": { @@ -65,19 +73,21 @@ The parameter server uses 4 services and an optional publisher, this needs to be On runtime, the variable `RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER` defines the RCLC executor handles required for a parameter server: -```C +```c // Executor init example with the minimum RCLC executor handles required rclc_executor_t executor = rclc_executor_get_zero_initialized_executor(); -rc = rclc_executor_init(&executor, &support.context, RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER, &allocator); +rc = rclc_executor_init( + &executor, &support.context, + RCLC_PARAMETER_EXECUTOR_HANDLES_NUMBER, &allocator); ``` - -## Callback + +## Callback When adding the parameter server to the executor, a callback can be configured. This callback will be executed after a parameter value is modified. A pointer to the changed parameter is passed as first and only argument. Example: -```C +```c void on_parameter_changed(Parameter * param) { // Get parameter name @@ -104,24 +114,24 @@ void on_parameter_changed(Parameter * param) } ``` Once the parameter server and the executor are initialized, the parameter server must be added to the executor in order to accept parameters commands from ROS2: -```C +```c // Add parameter server to executor including defined callback rc = rclc_executor_add_parameter_server(&executor, ¶m_server, on_parameter_changed); ``` Note that this callback is optional as its just an event information for the user. To use the parameter server without a callback: -```C +```c // Add parameter server to executor without callback rc = rclc_executor_add_parameter_server(&executor, ¶m_server, NULL); ``` -## Add a parameter +## Add a parameter // TODO: improve explanation of types - Bool parameter -```C +```c const char * parameter_name = "parameter_bool"; bool param_value = true; @@ -141,7 +151,7 @@ if (RCL_RET_OK != rc) { ``` - Integer parameter -```C +```c const char * parameter_name = "parameter_int"; int param_value = 100; @@ -156,7 +166,7 @@ rc = rclc_parameter_get_int(¶m_server, parameter_name, ¶m_value); ``` - Double parameter -```C +```c const char * parameter_name = "parameter_double"; double param_value = 0.15; @@ -170,11 +180,11 @@ rc = rclc_parameter_set_double(¶m_server, parameter_name, param_value); rc = rclc_parameter_get_double(¶m_server, parameter_name, ¶m_value); ``` -## Cleaning up +## Cleaning up To destroy an initialized parameter server: -```C +```c // Delete parameter server rclc_parameter_server_fini(¶m_server, &node); ``` diff --git a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md index 09cf965c..2b34d6b5 100644 --- a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md +++ b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md @@ -1,5 +1,5 @@ --- -title: Publishers and Subscriptions +title: Publishers and Subscribers permalink: /docs/tutorials/programming_rcl_rclc/pub_sub/ --- @@ -7,81 +7,97 @@ ROS 2 publishers and subscribers are the basic communication mechanism between n Ready to use code related to this concepts can be found in [`micro-ROS-demos/rclc/int32_publisher`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_publisher/main.c) and [`micro-ROS-demos/rclc/int32_subscriber`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/int32_subscriber/main.c) folders. Fragments of code from this examples are used on this tutorial. -// TODO: add index? +- [Publisher](#publisher) + - [Initialization](#initialization) + - [Publish a message](#publish-a-message) +- [Subscription](#subscription) + - [Initialization](#initialization-1) + - [Callbacks](#callbacks) +- [Message initialization](#message-initialization) +- [Cleaning Up](#cleaning-up) -## Publisher +## Publisher -### Initialization +### Initialization Starting from a code where RCL is initialized and a micro-ROS node is created, there are tree ways to initialize a publisher depending on the desired quality-of-service configuration: - + - Reliable (default): - ```C + ```c // Publisher object rcl_publisher_t publisher; const char * topic_name = "test_topic"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); // Creates a reliable rcl publisher - rcl_ret_t rc = rclc_publisher_init_default(&publisher, &node, &type_support, &topic_name); + rcl_ret_t rc = rclc_publisher_init_default( + &publisher, &node, + &type_support, &topic_name); if (RCL_RET_OK != rc) { ... // Handle error return -1; } ``` - + - Best effort: - ```C + ```c // Publisher object rcl_publisher_t publisher; const char * topic_name = "test_topic"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); // Creates a best effort rcl publisher - rcl_ret_t rc = rclc_publisher_init_best_effort(&publisher, &node, &type_support, &topic_name); + rcl_ret_t rc = rclc_publisher_init_best_effort( + &publisher, &node, + &type_support, &topic_name); if (RCL_RET_OK != rc) { ... // Handle error return -1; } ``` - + - Custom QoS: - ```C + ```c // Publisher object rcl_publisher_t publisher; const char * topic_name = "test_topic"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); // Set publisher QoS const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default; // Creates a rcl publisher with customized quality-of-service options - rcl_ret_t rc = rclc_publisher_init(&publisher, &node, &type_support, &topic_name, qos_profile); + rcl_ret_t rc = rclc_publisher_init( + &publisher, &node, + &type_support, &topic_name, qos_profile); if (RCL_RET_OK != rc) { ... // Handle error return -1; } ``` - + For a detail on the available QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). -## Publish a message +### Publish a message To publish messages to the topic: -```C +```c // Int32 message object -std_msgs__msg__Int32 msg; +std_msgs__msg__Int32 msg; // Set message value msg.data = 0; @@ -98,24 +114,27 @@ if (rc != RCL_RET_OK) { For periodic publications, `rcl_publish` can be placed inside a timer callback. Check the [Executor and timers](../executor/) section for details. Note: `rcl_publish` is thread safe and can be called from multiple threads. - -## Subscription -### Initialization +## Subscription + +### Initialization The suscriptor initialization is almost identical to the publisher one: - Reliable (default): - ```C + ```c // Subscription object rcl_subscription_t subscriber; const char * topic_name = "test_topic"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); // Initialize a reliable subscriber - rcl_ret_t rc = rclc_subscription_init_default(&subscriber, &node, &type_support, &topic_name); + rcl_ret_t rc = rclc_subscription_init_default( + &subscriber, &node, + &type_support, &topic_name); if (RCL_RET_OK != rc) { ... // Handle error @@ -125,16 +144,19 @@ The suscriptor initialization is almost identical to the publisher one: - Best effort: - ```C + ```c // Subscription object rcl_subscription_t subscriber; const char * topic_name = "test_topic"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); // Initialize best effort subscriber - rcl_ret_t rc = rclc_subscription_init_best_effort(&subscriber, &node, &type_support, &topic_name); + rcl_ret_t rc = rclc_subscription_init_best_effort( + &subscriber, &node, + &type_support, &topic_name); if (RCL_RET_OK != rc) { ... // Handle error @@ -144,19 +166,22 @@ The suscriptor initialization is almost identical to the publisher one: - Custom QoS: - ```C + ```c // Subscription object rcl_subscription_t subscriber; const char * topic_name = "test_topic"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); // Set client QoS const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_default; // Initialize a subscriber with customized quality-of-service options - rcl_ret_t rc = rclc_subscription_init(&subscriber, &node, &type_support, &topic_name, qos_profile); + rcl_ret_t rc = rclc_subscription_init( + &subscriber, &node, + &type_support, &topic_name, qos_profile); if (RCL_RET_OK != rc) { ... // Handle error @@ -165,13 +190,13 @@ The suscriptor initialization is almost identical to the publisher one: ``` For a detail on the available QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). - -### Callbacks -The executor is responsible to call the configured callback when a message is published. + +### Callbacks +The executor is responsible to call the configured callback when a message is published. The function will have the message as its only argument, containing the values sent by the publisher: -// TODO: explain function prototype? -```C +// TODO: explain function prototype? +```c void subscription_callback(const void * msgin) { // Cast received message to used type @@ -184,13 +209,15 @@ void subscription_callback(const void * msgin) Once the subscriber and the executor are initialized, the subscriber callback must be added to the executor to receive incoming publications once its spinning: - -```C + +```c // Message object to receive publisher data std_msgs__msg__Int32 msg; // Add subscription to the executor -rcl_ret_t rc = rclc_executor_add_subscription(&executor, &subscriber, &msg, &subscription_callback, ON_NEW_DATA); +rcl_ret_t rc = rclc_executor_add_subscription( + &executor, &subscriber, &msg, + &subscription_callback, ON_NEW_DATA); if (RCL_RET_OK != rc) { ... // Handle error @@ -200,16 +227,16 @@ if (RCL_RET_OK != rc) { rclc_executor_spin(&executor); ``` -## Message initialization +## Message initialization Before publishing or receiving a message, it may be necessary to initialize its memory for types with strings or sequences. Check the [Handling messages memory in micro-ROS](../../advanced/handling_type_memory/) section for details. -## Cleaning Up +## Cleaning Up After finishing the publisher/subscriber, the node will no longer be advertising that it is publishing/listening on the topic. To destroy an initialized publisher or subscriber: -```C +```c // Destroy publisher rcl_publisher_fini(&publisher, &node); diff --git a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md index fab556c6..f927aef3 100644 --- a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md +++ b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md @@ -3,18 +3,22 @@ title: Quality of service permalink: /docs/tutorials/programming_rcl_rclc/qos/ --- -// Add benchmark results for Throughput and RTT to compare both modes? +// Add benchmark results for Throughput and RTT to compare both modes? // Explain custom QoS options - -## Reliable QoS + +- [Reliable QoS](#reliable-qos) +- [Best Effort](#best-effort) +- [Custom QoS configuration](#custom-qos-configuration) + +## Reliable QoS Reliable communication implies a confirmation for each message sent. This mode can detect errors in the communication process at the cost of increasing the message latency and the resources usage. This message confirmation proccess can increase blocking time on `rcl_publish` or executor spin calls as reliable publishers, services and clients will wait for acknowledgement for each sent message. `rmw-microxrcedds` offers an API to individually configure the acknowledgement timeout on them: - ```C + ```c // Confirmation timeout in milliseconds - int ack_timeout = 1000; + int ack_timeout = 1000; // Set reliable publisher timeout rc = rmw_uros_set_publisher_session_timeout(&publisher, ack_timeout); @@ -30,16 +34,16 @@ This message confirmation proccess can increase blocking time on `rcl_publish` o return -1; } ``` - + The default value for all publishers is configured at compilation time by the cmake variable `RMW_UXRCE_PUBLISH_RELIABLE_TIMEOUT`. -## Best Effort +## Best Effort In best effort mode no acknowledgement is needed, the messages sent are expected to be received. This method improves publication throughput and reduces resources usage but is vulnerable to communication errors. -## Custom QoS configuration +## Custom QoS configuration -```C +```c /// ROS MiddleWare quality of service profile. typedef struct RMW_PUBLIC_TYPE rmw_qos_profile_t { diff --git a/_docs/tutorials/programming_rcl_rclc/service/services.md b/_docs/tutorials/programming_rcl_rclc/service/services.md index e4b8c844..581b1e48 100644 --- a/_docs/tutorials/programming_rcl_rclc/service/services.md +++ b/_docs/tutorials/programming_rcl_rclc/service/services.md @@ -7,64 +7,83 @@ ROS 2 services are another communication mechanism between nodes. Services imple Ready to use code related to this concepts can be found in [`micro-ROS-demos/rclc/addtwoints_server`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_server/main.c) and [`micro-ROS-demos/rclc/addtwoints_client`](https://github.com/micro-ROS/micro-ROS-demos/blob/foxy/rclc/addtwoints_client/main.c) folders. Fragments of code from this examples are used on this tutorial. -## Service server - -### Initialization +- [Service server](#service-server) + - [Initialization](#initialization) + - [Callback](#callback) +- [Service Client](#service-client) + - [Initialization](#initialization-1) + - [Callback](#callback-1) + - [Send a request](#send-a-request) +- [Message initialization](#message-initialization) +- [Cleaning Up](#cleaning-up) + +## Service server + +### Initialization Starting from a code where RCL is initialized and a micro-ROS node is created, there are tree ways to initialize a service server: - -- Reliable (default): - ```C +- Reliable (default): + + ```c // Service server object rcl_service_t service; const char * service_name = "/addtwoints"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); // Initialize server with default configuration - rcl_ret_t rc = rclc_service_init_default(&service, &node, type_support, service_name); + rcl_ret_t rc = rclc_service_init_default( + &service, &node, + type_support, service_name); if (rc != RCL_RET_OK) { ... // Handle error return -1; } ``` - -- Best effort: - ```C +- Best effort: + + ```c // Service server object rcl_service_t service; const char * service_name = "/addtwoints"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); // Initialize server with default configuration - rcl_ret_t rc = rclc_service_init_best_effort(&service, &node, type_support, service_name); + rcl_ret_t rc = rclc_service_init_best_effort( + &service, &node, + type_support, service_name); if (rc != RCL_RET_OK) { ... // Handle error return -1; } ``` - -- Custom QoS: - ```C +- Custom QoS: + + ```c // Service server object rcl_service_t service; const char * service_name = "/addtwoints"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); // Set service QoS const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_services_default; // Initialize server with customized quality-of-service options - rcl_ret_t rc = rclc_service_init(&service, &node, type_support, service_name, qos_profile); + rcl_ret_t rc = rclc_service_init( + &service, &node, type_support, + service_name, qos_profile); if (rc != RCL_RET_OK) { ... // Handle error @@ -74,14 +93,14 @@ Starting from a code where RCL is initialized and a micro-ROS node is created, t For a detail on the available QoS options and the advantages and disadvantages between reliable and best effort modes, check the [QoS tutorial](../qos/). -### Callback +### Callback Once a request arrives, the executor will call the configured callback with the request and response messages as arguments. The request message contains the values sent by the client, the response_msg should be modified here as it will be delivered after the callback returns. Using `AddTwoInts.srv` type definition as an example: -```C +```c int64 a int64 b --- @@ -90,29 +109,33 @@ int64 sum The client request message will contain two integers `a` and `b`, and expects the `sum` of them as a response: -```C +```c void service_callback(const void * request_msg, void * response_msg){ // Cast messages to expected types - example_interfaces__srv__AddTwoInts_Request * req_in = (example_interfaces__srv__AddTwoInts_Request *) request_msg; - example_interfaces__srv__AddTwoInts_Response * res_in = (example_interfaces__srv__AddTwoInts_Response *) response_msg; + example_interfaces__srv__AddTwoInts_Request * req_in = + (example_interfaces__srv__AddTwoInts_Request *) request_msg; + example_interfaces__srv__AddTwoInts_Response * res_in = + (example_interfaces__srv__AddTwoInts_Response *) response_msg; // Handle request message and set the response message values printf("Client requested sum of %d and %d.\n", (int) req_in->a, (int) req_in->b); res_in->sum = req_in->a + req_in->b; } ``` - + Note that it is necessary to cast each message to the expected type Once the service and the executor are initialized, the service callback must be added to the executor in order to process incoming requests once the executor is spinning: -```C +```c // Service message objects example_interfaces__srv__AddTwoInts_Response response_msg; example_interfaces__srv__AddTwoInts_Request request_msg; // Add server callback to the executor -rc = rclc_executor_add_service(&executor, &service, &request_msg, &response_msg, service_callback); +rc = rclc_executor_add_service( + &executor, &service, &request_msg, + &response_msg, service_callback); if (rc != RCL_RET_OK) { ... // Handle error @@ -122,81 +145,91 @@ if (rc != RCL_RET_OK) { // Spin executor to receive requests rclc_executor_spin(&executor); ``` - -## Service Client -### Initialization +## Service Client + +### Initialization The service client initialization is almost identical to the server one: - Reliable (default): - ```C + ```c // Service client object rcl_client_t client; const char * service_name = "/addtwoints"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); // Initialize client with default configuration - rcl_ret_t rc = rclc_client_init_default(&client, &node, type_support, service_name); + rcl_ret_t rc = rclc_client_init_default( + &client, &node, + type_support, service_name); if (rc != RCL_RET_OK) { ... // Handle error return -1; } ``` - -- Best effort: - ```C +- Best effort: + + ```c // Service client object rcl_client_t client; const char * service_name = "/addtwoints"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); // Initialize client with default configuration - rcl_ret_t rc = rclc_client_init_best_effort(&client, &node, type_support, service_name); + rcl_ret_t rc = rclc_client_init_best_effort( + &client, &node, + type_support, service_name); if (rc != RCL_RET_OK) { ... // Handle error return -1; } ``` - -- Custom QoS: - - ```C + +- Custom QoS: + + ```c // Service client object rcl_client_t client; const char * service_name = "/addtwoints"; // Get message type support - const rosidl_message_type_support_t * type_support = ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); + const rosidl_message_type_support_t * type_support = + ROSIDL_GET_SRV_TYPE_SUPPORT(example_interfaces, srv, AddTwoInts); // Set client QoS const rmw_qos_profile_t * qos_profile = &rmw_qos_profile_services_default; // Initialize server with customized quality-of-service options - rcl_ret_t rc = rclc_client_init(&client, &node, type_support, service_name, qos_profile); + rcl_ret_t rc = rclc_client_init( + &client, &node, type_support, + service_name, qos_profile); if (rc != RCL_RET_OK) { ... // Handle error return -1; } ``` - -### Callback -The executor is responsible to call the configured callback when the service response arrives. + +### Callback +The executor is responsible to call the configured callback when the service response arrives. The function will have the response message as its only argument, containing the values sent by the server. It is necessary to cast the response message to the expected type. Example: -```C +```c void client_callback(const void * response_msg){ // Cast response message to expected type - example_interfaces__srv__AddTwoInts_Response * msgin = (example_interfaces__srv__AddTwoInts_Response * ) response_msg; + example_interfaces__srv__AddTwoInts_Response * msgin = + (example_interfaces__srv__AddTwoInts_Response * ) response_msg; // Handle response message printf("Received service response %ld + %ld = %ld\n", req.a, req.b, msgin->sum); @@ -205,7 +238,7 @@ void client_callback(const void * response_msg){ Once the client and the executor are initialized, the client callback must be added to the executor in order to receive the service response once the executor is spinning: -```C +```c // Response message object example_interfaces__srv__AddTwoInts_Response res; @@ -221,11 +254,11 @@ if (rc != RCL_RET_OK) { rclc_executor_spin(&executor); ``` -### Send a request +### Send a request Once the service client and server are configured, the service client can perform a request and spin the executor to get the reply. Following the example on `AddTwoInts.srv`: -```C +```c // Request message object (Must match initialized client type support) example_interfaces__srv__AddTwoInts_Request request_msg; @@ -236,7 +269,7 @@ request_msg.a = 24; request_msg.b = 42; // Sequence number of the request (Populated in rcl_send_request) -int64_t sequence_number; +int64_t sequence_number; // Send request rcl_send_request(&client, &request_msg, &sequence_number); @@ -245,15 +278,15 @@ rcl_send_request(&client, &request_msg, &sequence_number); rclc_executor_spin(&executor); ``` -## Message initialization +## Message initialization Before sending or receiving a message, it may be necessary to initialize its memory for types with strings or sequences. Check the [Handling messages memory in micro-ROS](../../advanced/handling_type_memory/) section for details. -## Cleaning Up +## Cleaning Up To destroy an initialized service or client: -```C +```c // Destroy service server and client rcl_service_fini(&service, &node); rcl_client_fini(&client, &node); From 706fe97df899a9c567a7d5df5602da5335c62a86 Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Tue, 24 Aug 2021 14:22:46 +0200 Subject: [PATCH 09/14] Update --- _data/docs.yml | 1 + .../micro-ROS/micro-ROS.md | 49 +++++++++++++++---- .../programming_rcl_rclc/node/node.md | 2 - .../tutorials/programming_rcl_rclc/qos/QoS.md | 3 +- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/_data/docs.yml b/_data/docs.yml index 07fbe17c..91133c78 100644 --- a/_data/docs.yml +++ b/_data/docs.yml @@ -72,6 +72,7 @@ - tutorials/programming_rcl_rclc/pub_sub - tutorials/programming_rcl_rclc/service - tutorials/programming_rcl_rclc/parameters + - tutorials/programming_rcl_rclc/executor - tutorials/programming_rcl_rclc/qos - tutorials/programming_rcl_rclc/micro-ROS diff --git a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md index 406765da..3fc7f5b2 100644 --- a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md +++ b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md @@ -3,31 +3,60 @@ title: micro-ROS utilities permalink: /docs/tutorials/programming_rcl_rclc/micro-ROS/ --- -// TODO +// TODO: Change section name - [Time sync](#time-sync) - [Ping agent](#ping-agent) +- [Continous serialization](#continous-serialization) ## Time sync +micro-ROS Clients can synchronize their epoch time with the connected Agent, this can be very useful when working in embedded environments that do not provide any time synchronization mechanism. +This utility is based on the NTP protocol, taking into account delays caused by the transport layer. An usage example can be found on [`micro-ROS-demos/rclc/epoch_synchronization`](https://github.com/micro-ROS/micro-ROS-demos/blob/galactic/rclc/epoch_synchronization/main.c). ```c -bool rmw_uros_epoch_synchronized(); -int64_t rmw_uros_epoch_millis(); -int64_t rmw_uros_epoch_nanos(); -rmw_ret_t rmw_uros_sync_session(const int timeout_ms); -``` +// Sync timeout +const int timeout_ms = 1000; + +// Synchronize time with the agent +rmw_uros_sync_session(timeout_ms); +if (rmw_uros_epoch_synchronized()) +{ + // Get time in milliseconds or nanoseconds + int64_t time_ms = rmw_uros_epoch_millis(); + int64_t time_ns = rmw_uros_epoch_nanos(); +} +``` + ## Ping agent +The Client can test the connection with the Agent with the ping utility. This functionality can be used even when the micro-ROS context has not yet been initialized, which is useful to test the connection before trying to connect to the Agent. An example can be found on [`micro-ROS-demos/rclc/ping_uros_agent`](https://github.com/micro-ROS/micro-ROS-demos/blob/galactic/rclc/ping_uros_agent/main.c). ```c -rmw_ret_t rmw_uros_ping_agent(const int timeout_ms, const uint8_t attempts); +// Timeout for each attempt +const int timeout_ms = 1000; + +// Number of attemps +const uint8_t attemps = 5; + +// Ping the agent +rmw_ret_t ping_result = rmw_uros_ping_agent(timeout_ms, attempts); + +if (RMW_RET_OK == ping_result) +{ + // micro-ROS Agent is reachable + ... +} +else +{ + // micro-ROS Agent is not available + ... +} ``` -- Init options ?? +*Note: `rmw_uros_ping_agent` is thread safe.* -- Discovery ?? +## Continous serialization -- Continous serialization ?? -```c void rmw_uros_set_continous_serialization_callbacks( rmw_publisher_t * publisher, diff --git a/_docs/tutorials/programming_rcl_rclc/node/node.md b/_docs/tutorials/programming_rcl_rclc/node/node.md index 6593e968..d7a05b23 100644 --- a/_docs/tutorials/programming_rcl_rclc/node/node.md +++ b/_docs/tutorials/programming_rcl_rclc/node/node.md @@ -38,8 +38,6 @@ ROS 2 nodes are the main participants on ROS 2 ecosystem. They will communicate - Create a node with custom options: - // TODO: explain possible options - The configuration of the node will also be applied to its future elements (Publishers, subscribers, services, ...).The API used to customize the node options differs between ROS2 distributions: Foxy: The `rcl_node_options_t` is used to configure the node diff --git a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md index f927aef3..c4c2cf60 100644 --- a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md +++ b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md @@ -43,6 +43,8 @@ In best effort mode no acknowledgement is needed, the messages sent are expected ## Custom QoS configuration +The user can customize their own QoS using the available `rmw_qos_profile_t` struct: + ```c /// ROS MiddleWare quality of service profile. typedef struct RMW_PUBLIC_TYPE rmw_qos_profile_t @@ -66,5 +68,4 @@ typedef struct RMW_PUBLIC_TYPE rmw_qos_profile_t /// If true, any ROS specific namespacing conventions will be circumvented. bool avoid_ros_namespace_conventions; } rmw_qos_profile_t; - ``` From fa9ed9c74bdf5229a98ae6b2ceef409b5c0693c4 Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Wed, 25 Aug 2021 12:38:24 +0200 Subject: [PATCH 10/14] Update executor section --- .../programming_rcl_rclc/executor/executor.md | 909 +++++++++++++++++- 1 file changed, 908 insertions(+), 1 deletion(-) diff --git a/_docs/tutorials/programming_rcl_rclc/executor/executor.md b/_docs/tutorials/programming_rcl_rclc/executor/executor.md index bdffc10a..b00f83e0 100644 --- a/_docs/tutorials/programming_rcl_rclc/executor/executor.md +++ b/_docs/tutorials/programming_rcl_rclc/executor/executor.md @@ -3,4 +3,911 @@ title: Executor and timers permalink: /docs/tutorials/programming_rcl_rclc/executor/ --- -// TODO: Use existing parts of previous tutorial if possible \ No newline at end of file +- [Timers](#timers) + - [Initialization](#initialization) + - [Callback](#callback) + - [Cleaning Up](#cleaning-up) +- [Lifecycle](#lifecycle) + - [Initialization](#initialization-1) + - [Callbacks](#callbacks) + - [Running](#running) + - [Cleaning Up](#cleaning-up-1) + - [Limitations](#limitations) +- [Executor](#executor) + - [Example 1: 'Hello World'](#example-1-hello-world) + - [Example 2: Triggered execution](#example-2-triggered-execution) + +## Timers + +Timers can be created and added to the executor, which will call the timer callback periodically once it is spinning. +They are usually used to handle periodic publications or events. + +### Initialization + +```c +// Timer period on nanoseconds +const unsigned int timer_period = RCL_MS_TO_NS(1000); + +// Create and initialize timer object +rcl_timer_t timer; +rcl_ret_t rc = rclc_timer_init_default(&timer, &support, timer_period, timer_callback); + +// Add to the executor +rc = rclc_executor_add_timer(&executor, &timer); + +if (rc != RCL_RET_OK) { + ... // Handle error + return -1; +} +``` + +### Callback + +The callback gives a pointer to the associated timer and the time elapsed since the previous call or since the timer was created if it is the first call to the callback. + +```c +void timer_callback(rcl_timer_t * timer, int64_t last_call_time) +{ + printf("Last callback time: %ld\n", last_call_time); + + if (timer != NULL) { + // Perform actions + ... + } +} +``` + +During the callback the timer can be canceled or have its period and/or callback modified using the passed pointer. Check [rcl/timer.h](https://github.com/ros2/rcl/blob/galactic/rcl/include/rcl/timer.h) for details. + + +### Cleaning Up + +To destroy an initialized timer: + +```c +// Destroy timer +rcl_timer_fini(&timer); +``` + +This will deallocate used memory and make the timer invalid + +## Lifecycle + +The rclc lifecycle package provides convenience functions in C to bundle an rcl node with the ROS 2 Node Lifecycle state machine, similar to the [rclcpp Lifecycle Node](https://github.com/ros2/rclcpp/blob/master/rclcpp_lifecycle/include/rclcpp_lifecycle/lifecycle_node.hpp) for C++. Further information about ROS 2 node lifecycle can be found [here](https://design.ros2.org/articles/node_lifecycle.html) + +An usage example is given in the [rclc_examples](https://github.com/ros2/rclc/blob/master/rclc_examples/src/example_lifecycle_node.c) package. + +### Initialization + +Creation of a lifecycle node as a bundle of an rcl node and the rcl lifecycle state machine. Assuming an already initialized node and executor: + +```c +// Create rcl state machine +rcl_lifecycle_state_machine_t state_machine = +rcl_lifecycle_get_zero_initialized_state_machine(); + +// Create the lifecycle node +rclc_lifecycle_node_t my_lifecycle_node; +rcl_ret_t rc = rclc_make_node_a_lifecycle_node( + &my_lifecycle_node, + &node, + &state_machine, + &allocator); + +// Register lifecycle services on the allocator +rclc_lifecycle_add_get_state_service(&lifecycle_node, &executor); +rclc_lifecycle_add_get_available_states_service(&lifecycle_node, &executor); +rclc_lifecycle_add_change_state_service(&lifecycle_node, &executor); +``` + +*Note: Executor needsto be equipped with 1 handle per node and per service* + +### Callbacks + +Optional callbacks are supported to act on lifecycle state changes. Example: + +```c +rcl_ret_t my_on_configure() { + printf(" >>> my_lifecycle_node: on_configure() callback called.\n"); + return RCL_RET_OK; +} +``` + +To add them to the lifecycle node: + +```c +// Register lifecycle service callbacks +rclc_lifecycle_register_on_configure(&lifecycle_node, &my_on_configure); +rclc_lifecycle_register_on_activate(&lifecycle_node, &my_on_activate); +rclc_lifecycle_register_on_deactivate(&lifecycle_node, &my_on_deactivate); +rclc_lifecycle_register_on_cleanup(&lifecycle_node, &my_on_cleanup); +``` + +### Running + +To change states of the lifecycle node: + +```c +bool publish_transition = true; +rc += rclc_lifecycle_change_state( + &my_lifecycle_node, + lifecycle_msgs__msg__Transition__TRANSITION_CONFIGURE, + publish_transition); + +rc += rclc_lifecycle_change_state( + &my_lifecycle_node, + lifecycle_msgs__msg__Transition__TRANSITION_ACTIVATE, + publish_transition); +``` + +Except for error processing transitions, transitions are usually triggered from outside, e.g., by ROS 2 services. + +### Cleaning Up + +To clean everything up, simply do + +```c +rc += rcl_lifecycle_node_fini(&my_lifecycle_node, &allocator); +``` + +### Limitations + +Lifecycle services cannot yet be called via ros2 lifecycle client (`ros2 lifecycle set /node ...`). Instead use the ros2 service CLI, (Example: `ros2 service call /node/change_state lifecycle_msgs/ChangeState "{transition: {id: 1, label: configure}}"`). + +## Executor + +The rclc Executor provides a C API to manage the execution of subscription and timer callbacks, similar to the [rclcpp Executor](https://github.com/ros2/rclcpp/blob/master/rclcpp/include/rclcpp/executor.hpp) for C++. The rclc Executor is optimized for resource-constrained devices and provides additional features that allow the manual implementation of deterministic schedules with bounded end-to-end latencies. + +In this section we provide two examples: + +* Example 1: Hello-World example consisting of one executor and one publisher, timer and subscription. +* Example 2: Triggered execution example, demonstrating the capability of synchronizing the execution of callbacks based on the availability of new messages + +Further information about the rclc Executor and its API can be found [rclc](https://github.com/ros2/rclc/tree/master/rclc#rclc-executor) repository, including further examples for using the rclc Executor in mobile robotics scenarios and real-time embedded applications. + +### Example 1: 'Hello World' + +To start with, we provide a very simple example for an rclc Executor with one timer and one subscription, so to say, a 'Hello world' example. It consists of a publisher, sending a 'hello world' message to a subscriber, which then prints out the received message on the console. + +First, you include some header files, in particular the [rclc/rclc.h](https://github.com/ros2/rclc/blob/master/rclc/include/rclc/rclc.h) and [rclc/executor.h](https://github.com/ros2/rclc/blob/master/rclc/include/rclc/executor.h). + +```c +#include +#include +#include +#include +``` + +We define a publisher and two strings, which will be used later. + +```c +rcl_publisher_t my_pub; +std_msgs__msg__String pub_msg; +std_msgs__msg__String sub_msg; +``` + +The subscription callback casts the message parameter `msgin` to an equivalent type of `std_msgs::msg::String` in C and prints out the received message. + +```c +void my_subscriber_callback(const void * msgin) +{ + const std_msgs__msg__String * msg = (const std_msgs__msg__String *)msgin; + if (msg == NULL) { + printf("Callback: msg NULL\n"); + } else { + printf("Callback: I heard: %s\n", msg->data.data); + } +} +``` + +The timer callback publishes the message `pub_msg` with the publisher `my_pub`, which is initialized later in `main()`. + +```c +void my_timer_callback(rcl_timer_t * timer, int64_t last_call_time) +{ + rcl_ret_t rc; + UNUSED(last_call_time); + if (timer != NULL) { + rc = rcl_publish(&my_pub, &pub_msg, NULL); + if (rc == RCL_RET_OK) { + printf("Published message %s\n", pub_msg.data.data); + } else { + printf("Error in timer_callback: Message %s could not be published\n", pub_msg.data.data); + } + } else { + printf("Error in timer_callback: timer parameter is NULL\n"); + } +} +``` + +After defining the callback functions, we present now the `main()` function. First, some initialization is necessary to create later rcl objects. That is an `allocator` for dynamic memory allocation, and a `support` object, which contains some rcl-objects simplifying the initialization of an rcl-node, an rcl-subscription, an rcl-timer and an rclc-executor. + +```c +int main(int argc, const char * argv[]) +{ + rcl_allocator_t allocator = rcl_get_default_allocator(); + rclc_support_t support; + rcl_ret_t rc; + + // create init_options + rc = rclc_support_init(&support, argc, argv, &allocator); + if (rc != RCL_RET_OK) { + printf("Error rclc_support_init.\n"); + return -1; + } +``` + +Next, you define a ROS 2 node `my_node` and initialize it with `rclc_executor_init_default()`: + +```c + // create rcl_node + rcl_node_t my_node; + rc = rclc_node_init_default(&my_node, "node_0", "executor_examples", &support); + if (rc != RCL_RET_OK) { + printf("Error in rclc_node_init_default\n"); + return -1; + } +``` + +You can create a publisher to publish topic 'topic_0' with type std_msg::msg::String with the following code: + +```c +const char * topic_name = "topic_0"; +const rosidl_message_type_support_t * my_type_support = + ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String); + +rc = rclc_publisher_init_default(&my_pub, &my_node, my_type_support, topic_name); +if (RCL_RET_OK != rc) { + printf("Error in rclc_publisher_init_default %s.\n", topic_name); + return -1; +} +``` + +Note, that variable `my_pub` was defined globally, so it can be used by the timer callback. + +You can create a timer `my_timer` with a period of one second, which executes the callback `my_timer_callback` like this: + +```c + rcl_timer_t my_timer; + const unsigned int timer_timeout = 1000; // in ms + rc = rclc_timer_init_default(&my_timer, &support, RCL_MS_TO_NS(timer_timeout), my_timer_callback); + if (rc != RCL_RET_OK) { + printf("Error in rcl_timer_init_default.\n"); + return -1; + } else { + printf("Created timer with timeout %d ms.\n", timer_timeout); + } +``` + +The string `Hello World!` can be assigned directly to the message of the publisher `pub_msg.data`. First the publisher message is initialized with `std_msgs__msg__String__init`. Then you need to allocate memory for `pub_msg.data.data`, set the maximum capacity `pub_msg.data.capacity` and set the length of the message `pub_msg.data.size` accordingly. You can assign the content of the message with `snprintf` of `pub_msg.data.data`. + +```c + // assign message to publisher + std_msgs__msg__String__init(&pub_msg); + const unsigned int PUB_MSG_CAPACITY = 20; + pub_msg.data.data = malloc(PUB_MSG_CAPACITY); + pub_msg.data.capacity = PUB_MSG_CAPACITY; + snprintf(pub_msg.data.data, pub_msg.data.capacity, "Hello World!"); + pub_msg.data.size = strlen(pub_msg.data.data); +``` + +A subscription `my_sub`can be defined like this: + +```c + rcl_subscription_t my_sub = rcl_get_zero_initialized_subscription(); + rc = rclc_subscription_init_default(&my_sub, &my_node, my_type_support, topic_name); + if (rc != RCL_RET_OK) { + printf("Failed to create subscriber %s.\n", topic_name); + return -1; + } else { + printf("Created subscriber %s:\n", topic_name); + } +``` + +The global message for this subscription `sub_msg` needs to be initialized with: + +```c + std_msgs__msg__String__init(&sub_msg); +``` + +Now, all preliminary steps are done, and you can define and initialized the rclc executor with: + +```c + rclc_executor_t executor; + executor = rclc_executor_get_zero_initialized_executor(); +``` + +In the next step, executor is initialized with the ROS 2 `context`, the number of communication objects `num_handles` and an `allocator`. The number of communication objects defines the total number of timers and subscriptions, the executor shall manage. In this example, the executor will be setup with one timer and one subscription. + +```c + // total number of handles = #subscriptions + #timers + unsigned int num_handles = 1 + 1; + rclc_executor_init(&executor, &support.context, num_handles, &allocator); +``` + +Now, you can add a subscription with the function `rclc_c_executor_add_subscription` with the previously defined subscription `my_sub`, its message `sub_msg`and its callback `my_subscriber_callback`: + +```c +rc = rclc_executor_add_subscription(&executor, &my_sub, &sub_msg, &my_subscriber_callback, ON_NEW_DATA); +if (rc != RCL_RET_OK) { + printf("Error in rclc_executor_add_subscription. \n"); +} +``` + +The option `ON_NEW_DATA` selects the execution semantics of the spin-method. In this example, the callback of the subscription `my_sub`is only called if new data is available. + +Note: Another execution semantics is `ALWAYS`, which means, that the subscription callback is always executed when the spin-method of the executor is called. This option might be useful in cases in which the callback shall be executed at a fixed rate irrespective of new data is available or not. If you choose this option, then the callback will be executed with message argument `NULL` if no new data is available. Therefore you need to make sure, that your callback also accepts `NULL` as message argument. + +Likewise, you can add the timer `my_timer` with the function `rclc_c_executor_add_timer`: + +```c +rclc_executor_add_timer(&executor, &my_timer); +if (rc != RCL_RET_OK) { + printf("Error in rclc_executor_add_timer.\n"); +} +``` + +A key feature of the rclc Executor is that the order of these `rclc-executor-add-*`-functions matters. The order in which these functions are called defines the static processing order of the callbacks when the spin-function of the executor is running. + +In this example, the timer was added to the executor before the subscription. Therefore, if the timer is ready and also a new message for the subscription is available, then the timer is executed first and after it the subscription. Such a behavior cannot be defined currently with the rclcpp Executor and is useful to implement a deterministic execution semantics. + +Finally, you can run the executor with `rclc_executor_spin()`: + +```c + rclc_executor_spin(&executor); +``` + +This function runs forever without coming back. In this example, however, we want to publish the message only ten times. Therefore we are using the spin-method `rclc_executor_spin_some`, which spins only once and returns. The wait timeout for checking for new messages at the DDS-queue or waiting timers to get ready is configured to be one second. + +```c +for (unsigned int i = 0; i < 10; i++) { + // timeout specified in nanoseconds (here 1s) + rclc_executor_spin_some(&executor, 1000 * (1000 * 1000)); +} +``` + +At the end, you need to free dynamically allocated memory: + +```c + // clean up + rc = rclc_executor_fini(&executor); + rc += rcl_publisher_fini(&my_pub, &my_node); + rc += rcl_timer_fini(&my_timer); + rc += rcl_subscription_fini(&my_sub, &my_node); + rc += rcl_node_fini(&my_node); + rc += rclc_support_fini(&support); + std_msgs__msg__String__fini(&pub_msg); + std_msgs__msg__String__fini(&sub_msg); + + if (rc != RCL_RET_OK) { + printf("Error while cleaning up!\n"); + return -1; + } +return 0; +} // main +``` + +This completes the example. The source code can be found in the package rclc-examples [rclc-examples/example_executor_convenience.c](https://github.com/ros2/rclc/blob/master/rclc_examples/src/example_executor_convenience.c). + +### Example 2: Triggered execution + +In robotic applications often multiple sensors are used to improve localization precision. These sensors can have different frequencies, for example, a high frequency IMU sensor and a low frequency laser scanner. One way is to trigger execution upon arrival of a laser scan and only then evaluate the most recent data from the aggregated IMU data. + +This example demonstrates the additional feature of the rclc executor to trigger the execution of callbacks based on the availability of input data. + +We setup one executor with two publishers, one with 100ms and one with 1000ms period. Then we setup one executor for two subscriptions. Their callbacks shall both be executed if the message of the publisher with the lower frequency arrives. + +The output of this code example will look like this: + +```c +Created timer 'my_string_timer' with timeout 100 ms. +Created 'my_int_timer' with timeout 1000 ms. +Created subscriber topic_0: +Created subscriber topic_1: +Executor_pub: number of DDS handles: 2 +Executor_sub: number of DDS handles: 2 +Published: Hello World! 0 +Published: Hello World! 1 +Published: Hello World! 2 +Published: Hello World! 3 +Published: Hello World! 4 +Published: Hello World! 5 +Published: Hello World! 6 +Published: Hello World! 7 +Published: Hello World! 8 +Published: Hello World! 9 +Published: 0 +Callback 1: Hello World! 9 <--- +Callback 2: 0 <--- +Published: Hello World! 10 +Published: Hello World! 11 +Published: Hello World! 12 +Published: Hello World! 13 +Published: Hello World! 14 +Published: Hello World! 15 +Published: Hello World! 16 +Published: Hello World! 17 +Published: Hello World! 18 +Published: Hello World! 19 +Published: 1 +Callback 1: Hello World! 19 <--- +Callback 2: 1 <--- +``` + +This output shows, that the callbacks are executed, only if both message have received new data. In that case, the latest data of high-frequency topic is used. + +You learn in this tutorial + +* how to use pre-defined trigger conditions +* how to write custom-defined trigger conditions +* how to run multiple executors +* how to setup quality-of-service parameters for a subscription + +We start with the necessary includes for string and int messages, `` and `std_msgs/msg/int32.h` respectivly. Then the necessary includes follow for the rclc convenience functions `rclc.h` and the the rclc executor `executor.h`: + +```c +#include +#include +#include +#include + +#include +#include +``` + +Then, global variables for the publishers and subscriptions as well as their messages are defined, which are initialized in the `main()` function and used in the corresponding callbacks: + +```c +rcl_publisher_t my_pub; +rcl_publisher_t my_int_pub; +std_msgs__msg__String sub_msg; +std_msgs__msg__Int32 pub_int_msg; +int pub_int_value; +std_msgs__msg__Int32 sub_int_msg; +int pub_string_value; +``` + +For the custom-defined trigger conditions, the type `pub_trigger_object_t` and the type `sub_trigger_object_t` are defined. + +```c +typedef struct +{ + rcl_timer_t * timer1; + rcl_timer_t * timer2; +} pub_trigger_object_t; + +typedef struct +{ + rcl_subscription_t * sub1; + rcl_subscription_t * sub2; +} sub_trigger_object_t; +``` + +The executor for the publishers shall publish when any of corresponding timers for the publishers is ready. That is the or-logic. You could also use the predefined `rclc_executor_trigger_any` trigger condition, but this example shows how you can write your own trigger conditions. + +In principle, the condition gets a list of handles, the length of this list, and the pre-defined condition type. In this case, we expect `pub_trigger_object_t`. First, the parameter `obj` is cased to this type (`comm_obj`). Then, each element of the handle list is checked for new data (or a timer is ready) by evaluating the field `handles[i].data_available` and its handle pointer is compared to the pointer of the communicatoin object. If at least one timer is ready, then the trigger condition returns true. + +```c +bool pub_trigger(rclc_executor_handle_t * handles, unsigned int size, void * obj) +{ + if (handles == NULL) { + printf("Error in pub_trigger: 'handles' is a NULL pointer\n"); + return false; + } + if (obj == NULL) { + printf("Error in pub_trigger: 'obj' is a NULL pointer\n"); + return false; + } + pub_trigger_object_t * comm_obj = (pub_trigger_object_t *) obj; + bool timer1 = false; + bool timer2 = false; + //printf("pub_trigger ready set: "); + for (unsigned int i = 0; i < size; i++) { + if (handles[i].data_available) { + void * handle_ptr = rclc_executor_handle_get_ptr(&handles[i]); + if (handle_ptr == comm_obj->timer1) { + timer1 = true; + } + if (handle_ptr == comm_obj->timer2) { + timer2 = true; + } + } + } + return (timer1 || timer2); +} +``` + +The trigger condition for the subscription `sub_trigger`shall implement an AND-logic. That is, only if both subscriptions have received a new message, then the executor shall start processing the callbacks. + +The implementation is analogous to `pub_trigger`. The only difference is, that this trigger returns true, if both subscriptions have been found in the handle list. This is implemented in the condition `sub1 && sub2` of the last if-statement. + +```c +bool sub_trigger(rclc_executor_handle_t * handles, unsigned int size, void * obj) +{ + if (handles == NULL) { + printf("Error in sub_trigger: 'handles' is a NULL pointer\n"); + return false; + } + if (obj == NULL) { + printf("Error in sub_trigger: 'obj' is a NULL pointer\n"); + return false; + } + sub_trigger_object_t * comm_obj = (sub_trigger_object_t *) obj; + bool sub1 = false; + bool sub2 = false; + //printf("sub_trigger ready set: "); + for (unsigned int i = 0; i < size; i++) { + if (handles[i].data_available == true) { + void * handle_ptr = rclc_executor_handle_get_ptr(&handles[i]); + + if (handle_ptr == comm_obj->sub1) { + sub1 = true; + } + if (handle_ptr == comm_obj->sub2) { + sub2 = true; + } + } + } + return (sub1 && sub2); +} +``` + +Like in the Hello-World example, the subscription callbacks just prints out the received message. + +The `my_string_subscriber` callback prints out the string of the message `msg->data.data`: + +```c +void my_string_subscriber_callback(const void * msgin) +{ + const std_msgs__msg__String * msg = (const std_msgs__msg__String *)msgin; + if (msg == NULL) { + printf("my_string_subscriber_callback: msgin is NULL\n"); + } else { + printf("Callback 1: %s\n", msg->data.data); + } +} +``` + +The integer callback prints out the received integer `msg->data`: + +```c +void my_int_subscriber_callback(const void * msgin) +{ + const std_msgs__msg__Int32 * msg = (const std_msgs__msg__Int32 *)msgin; + if (msg == NULL) { + printf("my_int_subscriber_callback: msgin is NULL\n"); + } else { + printf("Callback 2: %d\n", msg->data); + } +} +``` + +To publish messages with different frequencies, we setup two timers. +One timer to publish a string message, the `my_timer_string_callback` and one timer to publish the integer, the `my_timer_int_callback`. + +In the `my_timer_string_callback`, the message `pub_msg` is created and filled with the string `Hello World` plus an integer, which is incremented by one, each time the timer callback is called. The the message is published with `rcl_publish()` + +The macro `UNUSED` is a workaround for the linter warning, that the second parameter `last_call_time` is not used. + +```c +#define UNUSED(x) (void)x; + +void my_timer_string_callback(rcl_timer_t * timer, int64_t last_call_time) +{ + rcl_ret_t rc; + UNUSED(last_call_time); + if (timer != NULL) { + //printf("Timer: time since last call %d\n", (int) last_call_time); + + std_msgs__msg__String pub_msg; + std_msgs__msg__String__init(&pub_msg); + const unsigned int PUB_MSG_CAPACITY = 20; + pub_msg.data.data = malloc(PUB_MSG_CAPACITY); + pub_msg.data.capacity = PUB_MSG_CAPACITY; + snprintf(pub_msg.data.data, pub_msg.data.capacity, "Hello World!%d", pub_string_value++); + pub_msg.data.size = strlen(pub_msg.data.data); + + rc = rcl_publish(&my_pub, &pub_msg, NULL); + if (rc == RCL_RET_OK) { + printf("Published: %s\n", pub_msg.data.data); + } else { + printf("Error in my_timer_string_callback: publishing message %s\n", pub_msg.data.data); + } + std_msgs__msg__String__fini(&pub_msg); + } else { + printf("Error in my_timer_string_callback: timer parameter is NULL\n"); + } +} +``` + +Likewise, the `my_timer_int_callback` increments the integer value `pub_int_value` in every call and assigns it to the message field `pub_int_msg.data`. Then the message is published with `rcl_publish()` + +```c +void my_timer_int_callback(rcl_timer_t * timer, int64_t last_call_time) +{ + rcl_ret_t rc; + UNUSED(last_call_time); + if (timer != NULL) { + //printf("Timer: time since last call %d\n", (int) last_call_time); + pub_int_msg.data = pub_int_value++; + rc = rcl_publish(&my_int_pub, &pub_int_msg, NULL); + if (rc == RCL_RET_OK) { + printf("Published: %d\n", pub_int_msg.data); + } else { + printf("Error in my_timer_int_callback: publishing message %d\n", pub_int_msg.data); + } + } else { + printf("Error in my_timer_int_callback: timer parameter is NULL\n"); + } +} +``` + +Now were are all set for the `main()` function: + +```c +int main(int argc, const char * argv[]) +{ + rcl_allocator_t allocator = rcl_get_default_allocator(); + rclc_support_t support; + rcl_ret_t rc; + + // create init_options + rc = rclc_support_init(&support, argc, argv, &allocator); + if (rc != RCL_RET_OK) { + printf("Error rclc_support_init.\n"); + return -1; + } +``` + +First rcl is initialized with the `rclc_support_init` using the default `allocator`. The rclc-support objects are saved in `support`. Next, a node `my_node` with the name `node_0` and namespace `executor_examples` is created with: + +```c +// create rcl_node + rcl_node_t my_node; + rc = rclc_node_init_default(&my_node, "node_0", "executor_examples", &support); + if (rc != RCL_RET_OK) { + printf("Error in rclc_node_init_default\n"); + return -1; + } +``` + +A publisher `my_string_pub`, which publishes a string message and its corresponding timer `my_string_timer` with a 100ms period is created like this: + +```c +// create a publisher 1 +// - topic name: 'topic_0' +// - message type: std_msg::msg::String +const char * topic_name = "topic_0"; +const rosidl_message_type_support_t * my_type_support = ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String); + +rc = rclc_publisher_init_default(&my_string_pub, &my_node, my_type_support, topic_name); +if (RCL_RET_OK != rc) { + printf("Error in rclc_publisher_init_default %s.\n", topic_name); + return -1; +} + +// create timer 1 +// - publishes 'my_string_pub' every 'timer_timeout' ms +rcl_timer_t my_string_timer; +const unsigned int timer_timeout = 100; +rc = rclc_timer_init_default(&my_string_timer, &support, RCL_MS_TO_NS(timer_timeout), my_timer_string_callback); +if (rc != RCL_RET_OK) { + printf("Error in rclc_timer_init_default.\n"); + return -1; +} else { + printf("Created timer 'my_string_timer' with timeout %d ms.\n", timer_timeout); +} +``` + +Note that the previously defined `my_timer_string_callback` is connected to this timer. +Likewise, a second publisher `my_int_pub, which publishes an int message and its corresponding timer` my_int_timer` with 1000ms period, is created like this: + +```c +// create publisher 2 + // - topic name: 'topic_1' + // - message type: std_msg::msg::Int + const char * topic_name_1 = "topic_1"; + const rosidl_message_type_support_t * my_int_type_support = + ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32); + rc = rclc_publisher_init_default(&my_int_pub, &my_node, my_int_type_support, topic_name_1); + if (RCL_RET_OK != rc) { + printf("Error in rclc_publisher_init_default %s.\n", topic_name_1); + return -1; + } + + // create timer 2 + // - publishes 'my_int_pub' every 'timer_int_timeout' ms + rcl_timer_t my_int_timer; + const unsigned int timer_int_timeout = 10 * timer_timeout; + rc = rclc_timer_init_default(&my_int_timer, &support, RCL_MS_TO_NS(timer_int_timeout), my_timer_int_callback); + if (rc != RCL_RET_OK) { + printf("Error in rclc_timer_init_default.\n"); + return -1; + } else { + printf("Created timer with timeout %d ms.\n", timer_int_timeout); + } +``` + +Note that the `my_timer_int_callback` is connected to the `my_int_timer`. The data variables used for the publisher messages in the timer callbacks need to be initialized first: + +```c +std_msgs__msg__Int32__init(&int_pub_msg); +int_pub_value = 0; +string_pub_value = 0; +``` + +The first subscription `my_string_sub` is created with the function `rcl_subscription_init` because we change the quality-of-service parameter to 'last-is-best'. That is, a new message will overwrite the older message if it has not been processed by the subscription. Also the message `string_sub_msg` needs to be initialized. + +```c +// create subscription 1 + rcl_subscription_t my_string_sub = rcl_get_zero_initialized_subscription(); + rcl_subscription_options_t my_subscription_options = rcl_subscription_get_default_options(); + my_subscription_options.qos.depth = 0; // qos: last is best = register semantics + rc = rcl_subscription_init(&my_string_sub, &my_node, my_type_support, topic_name, &my_subscription_options); + + if (rc != RCL_RET_OK) { + printf("Failed to create subscriber %s.\n", topic_name); + return -1; + } else { + printf("Created subscriber %s:\n", topic_name); + } + // initialize subscription message + std_msgs__msg__String__init(&string_sub_msg); +``` + +The second subscription `my_int_sub` is created with the rclc convenience function `rclc_subscription_default` and the message `int_sub_msg` is properly initialized. + +```c +// create subscription 2 + rcl_subscription_t my_int_sub = rcl_get_zero_initialized_subscription(); + rc = rclc_subscription_init_default(&my_int_sub, &my_node, my_int_type_support, topic_name_1); + if (rc != RCL_RET_OK) { + printf("Failed to create subscriber %s.\n", topic_name_1); + return -1; + } else { + printf("Created subscriber %s:\n", topic_name_1); + } + // initialize subscription message + std_msgs__msg__Int32__init(&int_sub_msg); +``` + +In this example, we are using two executors, one to schedule the publishers, and one to schedule the subscriptions: + +```c +rclc_executor_t executor_pub; +rclc_executor_t executor_sub; +``` + +The executor `executor_pub` is first created with `rclc_executor_get_zero_initialized_executor()` and has two handles (aka 2 timers). + +```c +// Executor for publishing messages + unsigned int num_handles_pub = 2; + printf("Executor_pub: number of DDS handles: %u\n", num_handles_pub); + executor_pub = rclc_executor_get_zero_initialized_executor(); + rclc_executor_init(&executor_pub, &support.context, num_handles_pub, &allocator); + + rc = rclc_executor_add_timer(&executor_pub, &my_string_timer); + if (rc != RCL_RET_OK) { + printf("Error in rclc_executor_add_timer 'my_string_timer'.\n"); + } + + rc = rclc_executor_add_timer(&executor_pub, &my_int_timer); + if (rc != RCL_RET_OK) { + printf("Error in rclc_executor_add_timer 'my_int_timer'.\n"); + } +``` + +Both timers are added to the exececutor with the function `rclc_executor_add_timer`: + +```c +rc = rclc_executor_add_timer(&executor_pub, &my_string_timer); +if (rc != RCL_RET_OK) { + printf("Error in rclc_executor_add_timer 'my_string_timer'.\n"); +} + +rc = rclc_executor_add_timer(&executor_pub, &my_int_timer); +if (rc != RCL_RET_OK) { + printf("Error in rclc_executor_add_timer 'my_int_timer'.\n"); +} +``` + +Also the second publisher has two handles, the two subscriptions: + +```c +unsigned int num_handles_sub = 2; +printf("Executor_sub: number of DDS handles: %u\n", num_handles_sub); +executor_sub = rclc_executor_get_zero_initialized_executor(); +rclc_executor_init(&executor_sub, &support.context, num_handles_sub, &allocator); +``` + +Which are added with the function `rclc_executor_add_subscription`: + +```c +// add subscription to executor +rc = rclc_executor_add_subscription( + &executor_sub, &my_string_sub, &string_sub_msg, + &my_string_subscriber_callback, + ON_NEW_DATA); +if (rc != RCL_RET_OK) { + printf("Error in rclc_executor_add_subscription 'my_string_sub'. \n"); +} + +// add int subscription to executor +rc = rclc_executor_add_subscription( + &executor_sub, &my_int_sub, &int_sub_msg, + &my_int_subscriber_callback, + ON_NEW_DATA); +if (rc != RCL_RET_OK) { + printf("Error in rclc_executor_add_subscription 'my_int_sub'. \n"); +} +``` + +The trigger condition of the executor, which publishes messages, shall execute when any timer is ready. This can be configured with the function `rclc_executor_set_trigger` and the parameter `rclc_executor_trigger_any`. +While the executor for the subscriptions shall only execute if both messages have arrived. Therefore the trigger parameter `rclc_executor_trigger_any` can be used: + +```c +rc = rclc_executor_set_trigger(&executor_pub, rclc_executor_trigger_any, NULL); +rc = rclc_executor_set_trigger(&executor_sub, rclc_executor_trigger_all, NULL); +``` + +Finally, the executors spin-some functions can be started. The sleep-time between the executors is intended for communication time for DDS. + +```c +for (unsigned int i = 0; i < 100; i++) { + // timeout specified in ns (here: 1s) + rclc_executor_spin_some(&executor_pub, 1000 * (1000 * 1000)); + usleep(1000); // 1ms + rclc_executor_spin_some(&executor_sub, 1000 * (1000 * 1000)); +} +``` + +This example is concluded with the clean-up code: + +```c +// clean up +rc = rclc_executor_fini(&executor_pub); +rc += rclc_executor_fini(&executor_sub); +rc += rcl_publisher_fini(&my_string_pub, &my_node); +rc += rcl_publisher_fini(&my_int_pub, &my_node); +rc += rcl_timer_fini(&my_string_timer); +rc += rcl_timer_fini(&my_int_timer); +rc += rcl_subscription_fini(&my_string_sub, &my_node); +rc += rcl_subscription_fini(&my_int_sub, &my_node); +rc += rcl_node_fini(&my_node); +rc += rclc_support_fini(&support); + +std_msgs__msg__Int32__fini(&int_pub_msg); +std_msgs__msg__String__fini(&string_sub_msg); +std_msgs__msg__Int32__fini(&int_sub_msg); + +if (rc != RCL_RET_OK) { + printf("Error while cleaning up!\n"); + return -1; +} +return 0; +} +``` + +In case the default trigger conditions are not sufficient, then the user can define custom logic conditions. +The source code of the custom-programmed trigger condition has already been presented. +The following code will setup the executor accordingly: + +```c + pub_trigger_object_t comm_obj_pub; + comm_obj_pub.timer1 = &my_string_timer; + comm_obj_pub.timer2 = &my_int_timer; + + sub_trigger_object_t comm_obj_sub; + comm_obj_sub.sub1 = &my_string_sub; + comm_obj_sub.sub2 = &my_int_sub; + + rc = rclc_executor_set_trigger(&executor_pub, pub_trigger, &comm_obj_pub); + rc = rclc_executor_set_trigger(&executor_sub, sub_trigger, &comm_obj_sub); +``` + +The custom structs `pub_trigger_object_t` are used to save the pointer of the handles. The timers `my_string_timer` and `my_int_timer` for the publishing executor; and, likewise, the subscriptions `my_string_sub` and `my_int_sub` for the subscribing executor. The configuration is done also with the `rclc_executor_set_trigger` by passing the trigger function and the trigger object, e.g. `pub_trigger` and `comm_obj_pub` for the `executor_pub`, respectivly. + +The complete source code of this example can be found in the file [rclc-examples/example_executor_trigger.c](https://github.com/ros2/rclc/rclc_examples/example_executor_trigger.c). + + From d53cc4781da37a04505ed7063a6705d98cdeb631 Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Wed, 25 Aug 2021 14:21:41 +0200 Subject: [PATCH 11/14] Minor changes and Add Allocator explanation --- .../programming_rcl_rclc/executor/executor.md | 88 -------------- .../micro-ROS/micro-ROS.md | 111 ++++++++++++++++++ .../programming_rcl_rclc/node/node.md | 90 +++++++++++++- .../parameters/parameters.md | 17 +-- .../programming_rcl_rclc/pub_sub/pub_sub.md | 5 +- .../programming_rcl_rclc/service/services.md | 9 +- 6 files changed, 221 insertions(+), 99 deletions(-) diff --git a/_docs/tutorials/programming_rcl_rclc/executor/executor.md b/_docs/tutorials/programming_rcl_rclc/executor/executor.md index b00f83e0..d4e22472 100644 --- a/_docs/tutorials/programming_rcl_rclc/executor/executor.md +++ b/_docs/tutorials/programming_rcl_rclc/executor/executor.md @@ -7,12 +7,6 @@ permalink: /docs/tutorials/programming_rcl_rclc/executor/ - [Initialization](#initialization) - [Callback](#callback) - [Cleaning Up](#cleaning-up) -- [Lifecycle](#lifecycle) - - [Initialization](#initialization-1) - - [Callbacks](#callbacks) - - [Running](#running) - - [Cleaning Up](#cleaning-up-1) - - [Limitations](#limitations) - [Executor](#executor) - [Example 1: 'Hello World'](#example-1-hello-world) - [Example 2: Triggered execution](#example-2-triggered-execution) @@ -71,88 +65,6 @@ rcl_timer_fini(&timer); This will deallocate used memory and make the timer invalid -## Lifecycle - -The rclc lifecycle package provides convenience functions in C to bundle an rcl node with the ROS 2 Node Lifecycle state machine, similar to the [rclcpp Lifecycle Node](https://github.com/ros2/rclcpp/blob/master/rclcpp_lifecycle/include/rclcpp_lifecycle/lifecycle_node.hpp) for C++. Further information about ROS 2 node lifecycle can be found [here](https://design.ros2.org/articles/node_lifecycle.html) - -An usage example is given in the [rclc_examples](https://github.com/ros2/rclc/blob/master/rclc_examples/src/example_lifecycle_node.c) package. - -### Initialization - -Creation of a lifecycle node as a bundle of an rcl node and the rcl lifecycle state machine. Assuming an already initialized node and executor: - -```c -// Create rcl state machine -rcl_lifecycle_state_machine_t state_machine = -rcl_lifecycle_get_zero_initialized_state_machine(); - -// Create the lifecycle node -rclc_lifecycle_node_t my_lifecycle_node; -rcl_ret_t rc = rclc_make_node_a_lifecycle_node( - &my_lifecycle_node, - &node, - &state_machine, - &allocator); - -// Register lifecycle services on the allocator -rclc_lifecycle_add_get_state_service(&lifecycle_node, &executor); -rclc_lifecycle_add_get_available_states_service(&lifecycle_node, &executor); -rclc_lifecycle_add_change_state_service(&lifecycle_node, &executor); -``` - -*Note: Executor needsto be equipped with 1 handle per node and per service* - -### Callbacks - -Optional callbacks are supported to act on lifecycle state changes. Example: - -```c -rcl_ret_t my_on_configure() { - printf(" >>> my_lifecycle_node: on_configure() callback called.\n"); - return RCL_RET_OK; -} -``` - -To add them to the lifecycle node: - -```c -// Register lifecycle service callbacks -rclc_lifecycle_register_on_configure(&lifecycle_node, &my_on_configure); -rclc_lifecycle_register_on_activate(&lifecycle_node, &my_on_activate); -rclc_lifecycle_register_on_deactivate(&lifecycle_node, &my_on_deactivate); -rclc_lifecycle_register_on_cleanup(&lifecycle_node, &my_on_cleanup); -``` - -### Running - -To change states of the lifecycle node: - -```c -bool publish_transition = true; -rc += rclc_lifecycle_change_state( - &my_lifecycle_node, - lifecycle_msgs__msg__Transition__TRANSITION_CONFIGURE, - publish_transition); - -rc += rclc_lifecycle_change_state( - &my_lifecycle_node, - lifecycle_msgs__msg__Transition__TRANSITION_ACTIVATE, - publish_transition); -``` - -Except for error processing transitions, transitions are usually triggered from outside, e.g., by ROS 2 services. - -### Cleaning Up - -To clean everything up, simply do - -```c -rc += rcl_lifecycle_node_fini(&my_lifecycle_node, &allocator); -``` - -### Limitations - -Lifecycle services cannot yet be called via ros2 lifecycle client (`ros2 lifecycle set /node ...`). Instead use the ros2 service CLI, (Example: `ros2 service call /node/change_state lifecycle_msgs/ChangeState "{transition: {id: 1, label: configure}}"`). ## Executor diff --git a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md index 3fc7f5b2..a0b2beac 100644 --- a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md +++ b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md @@ -5,10 +5,121 @@ permalink: /docs/tutorials/programming_rcl_rclc/micro-ROS/ // TODO: Change section name +- [Allocators](#allocators) + - [Custom allocator](#custom-allocator) - [Time sync](#time-sync) - [Ping agent](#ping-agent) - [Continous serialization](#continous-serialization) +## Allocators + + The allocator object wraps the dynamic memory allocation and deallocating methods used in micro-ROS + + ```c + // Get micro-ROS default allocator + rcl_allocator_t allocator = rcl_get_default_allocator(); + ``` + + The default allocator wraps the following methods: + + ```c + - allocate = wraps malloc() + - deallocate = wraps free() + - reallocate = wraps realloc() + - zero_allocate = wraps calloc() + - state = `NULL` + ``` + +### Custom allocator + +Working in embedded systems, the user might need to modify this default functions with its own memory allocation methods. +To archieve this, the user can modify the default allocator with its own methods: + +```c +// Get empty allocator +rcl_allocator_t custom_allocator = rcutils_get_zero_initialized_allocator(); + +// Set custom allocation methods +custom_allocator.allocate = microros_allocate; +custom_allocator.deallocate = microros_deallocate; +custom_allocator.reallocate = microros_reallocate; +custom_allocator.zero_allocate = microros_zero_allocate; + +// Set custom allocator as default +if (!rcutils_set_default_allocator(&custom_allocator)) { + ... // Handle error + return -1; +} +``` + +Custom methods prototypes and examples: + +- allocate: + + Allocates memory given a size, an error should be indicated by returning `NULL`: + + ```c + // Function prototype: + void * (*allocate)(size_t size, void * state); + + // Implementation example: + void * microros_allocate(size_t size, void * state){ + (void) state; + void * ptr = malloc(size); + return ptr; + } + ``` + +- deallocate + + Deallocate previously allocated memory, mimicking free(): + + ```c + // Function prototype: + void (* deallocate)(void * pointer, void * state); + + // Implementation example: + void microros_deallocate(void * pointer, void * state){ + (void) state; + free(pointer); + } + ``` + +- reallocate: + + Reallocate memory if possible, otherwise it deallocates and allocates: + + ```c + // Function prototype: + void * (*reallocate)(void * pointer, size_t size, void * state); + + // Implementation example: + void * microros_reallocate(void * pointer, size_t size, void * state){ + (void) state; + void * ptr = realloc(pointer, size); + return ptr; + } + ``` + +- zero_allocate: + + Allocate memory with all elements set to zero, given a number of elements and their size. An error should be indicated by returning `NULL`: + + ```c + // Function prototype: + void * (*zero_allocate)(size_t number_of_elements, size_t size_of_element, void * state); + + // Implementation example: + void * microros_zero_allocate(size_t number_of_elements, size_t size_of_element, void * state){ + (void) state; + void * ptr = malloc(number_of_elements * size_of_element); + memset(ptr, 0, number_of_elements * size_of_element); + return ptr; + } + ``` + + *Note: the `state` input argument is espected to be unused* + ## Time sync micro-ROS Clients can synchronize their epoch time with the connected Agent, this can be very useful when working in embedded environments that do not provide any time synchronization mechanism. This utility is based on the NTP protocol, taking into account delays caused by the transport layer. An usage example can be found on [`micro-ROS-demos/rclc/epoch_synchronization`](https://github.com/micro-ROS/micro-ROS-demos/blob/galactic/rclc/epoch_synchronization/main.c). diff --git a/_docs/tutorials/programming_rcl_rclc/node/node.md b/_docs/tutorials/programming_rcl_rclc/node/node.md index d7a05b23..00a4fe40 100644 --- a/_docs/tutorials/programming_rcl_rclc/node/node.md +++ b/_docs/tutorials/programming_rcl_rclc/node/node.md @@ -5,10 +5,15 @@ permalink: /docs/tutorials/programming_rcl_rclc/node/ ROS 2 nodes are the main participants on ROS 2 ecosystem. They will communicate between each other using publishers, subscriptions, services, etc. Further information about ROS 2 nodes can be found [here](https://docs.ros.org/en/galactic/Tutorials/Understanding-ROS2-Nodes.html) -// TODO: explain general micro-ROS initialization (allocator and support). Where? - [Initialization](#initialization) - [Cleaning Up](#cleaning-up) +- [Lifecycle](#lifecycle) + - [Initialization](#initialization-1) + - [Callbacks](#callbacks) + - [Running](#running) + - [Cleaning Up](#cleaning-up-1) + - [Limitations](#limitations) ## Initialization @@ -113,3 +118,86 @@ rcl_node_fini(&node); ``` This will delete the node from ROS2 graph, including any generated infrastructure on the agent (if possible) and used memory on the client. + +## Lifecycle + +The rclc lifecycle package provides convenience functions in C to bundle an rcl node with the ROS 2 Node Lifecycle state machine, similar to the [rclcpp Lifecycle Node](https://github.com/ros2/rclcpp/blob/master/rclcpp_lifecycle/include/rclcpp_lifecycle/lifecycle_node.hpp) for C++. Further information about ROS 2 node lifecycle can be found [here](https://design.ros2.org/articles/node_lifecycle.html) + +An usage example is given in the [rclc_examples](https://github.com/ros2/rclc/blob/master/rclc_examples/src/example_lifecycle_node.c) package. + +### Initialization + +Creation of a lifecycle node as a bundle of an rcl node and the rcl lifecycle state machine. Assuming an already initialized node and executor: + +```c +// Create rcl state machine +rcl_lifecycle_state_machine_t state_machine = +rcl_lifecycle_get_zero_initialized_state_machine(); + +// Create the lifecycle node +rclc_lifecycle_node_t my_lifecycle_node; +rcl_ret_t rc = rclc_make_node_a_lifecycle_node( + &my_lifecycle_node, + &node, + &state_machine, + &allocator); + +// Register lifecycle services on the allocator +rclc_lifecycle_add_get_state_service(&lifecycle_node, &executor); +rclc_lifecycle_add_get_available_states_service(&lifecycle_node, &executor); +rclc_lifecycle_add_change_state_service(&lifecycle_node, &executor); +``` + +*Note: Executor needsto be equipped with 1 handle per node and per service* + +### Callbacks + +Optional callbacks are supported to act on lifecycle state changes. Example: + +```c +rcl_ret_t my_on_configure() { + printf(" >>> my_lifecycle_node: on_configure() callback called.\n"); + return RCL_RET_OK; +} +``` + +To add them to the lifecycle node: + +```c +// Register lifecycle service callbacks +rclc_lifecycle_register_on_configure(&lifecycle_node, &my_on_configure); +rclc_lifecycle_register_on_activate(&lifecycle_node, &my_on_activate); +rclc_lifecycle_register_on_deactivate(&lifecycle_node, &my_on_deactivate); +rclc_lifecycle_register_on_cleanup(&lifecycle_node, &my_on_cleanup); +``` + +### Running + +To change states of the lifecycle node: + +```c +bool publish_transition = true; +rc += rclc_lifecycle_change_state( + &my_lifecycle_node, + lifecycle_msgs__msg__Transition__TRANSITION_CONFIGURE, + publish_transition); + +rc += rclc_lifecycle_change_state( + &my_lifecycle_node, + lifecycle_msgs__msg__Transition__TRANSITION_ACTIVATE, + publish_transition); +``` + +Except for error processing transitions, transitions are usually triggered from outside, e.g., by ROS 2 services. + +### Cleaning Up + +To clean everything up, simply do + +```c +rc += rcl_lifecycle_node_fini(&my_lifecycle_node, &allocator); +``` + +### Limitations + +Lifecycle services cannot yet be called via ros2 lifecycle client (`ros2 lifecycle set /node ...`). Instead use the ros2 service CLI, (Example: `ros2 service call /node/change_state lifecycle_msgs/ChangeState "{transition: {id: 1, label: configure}}"`). diff --git a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md index a371afa7..18438a2e 100644 --- a/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md +++ b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md @@ -3,7 +3,7 @@ title: Parameter server permalink: /docs/tutorials/programming_rcl_rclc/parameters/ --- -ROS 2 parameter allow the user to create variables on a node and manipulate/read them with different ROS2 commands. Further information about ROS 2 parameters can be found [here](https://docs.ros.org/en/galactic/Tutorials/Parameters/Understanding-ROS2-Parameters.html) +ROS 2 parameters allow the user to create variables on a node and manipulate/read them with different ROS2 commands. Further information about ROS 2 parameters can be found [here](https://docs.ros.org/en/galactic/Tutorials/Parameters/Understanding-ROS2-Parameters.html) Ready to use code related to this tutorial can be found in [`rclc/rclc_examples/src/example_parameter_server.c`](https://github.com/ros2/rclc/blob/master/rclc_examples/src/example_parameter_server.c). Fragments of code from this example is used on this tutorial. @@ -31,13 +31,15 @@ Note: micro-ROS parameter server is only supported on ROS2 galactic distribution } ``` - // TODO: explain options - Custom options: + + The user can configure whether to notify parameter changes to the rest of nodes (`notify_changed_over_dds`) and the maximum number of parameters (`max_params`) allowed on the `rclc_parameter_server_t` object: + ```c // Parameter server object rclc_parameter_server_t param_server; - // Define parameter server options + // Initialize parameter server options const rclc_parameter_options_t options = { .notify_changed_over_dds = true, .max_params = 4 }; @@ -125,12 +127,11 @@ Note that this callback is optional as its just an event information for the use rc = rclc_executor_add_parameter_server(&executor, ¶m_server, NULL); ``` - ## Add a parameter -// TODO: improve explanation of types +micro-ROS parameter server supports the following parameter types: -- Bool parameter +- Boolean: ```c const char * parameter_name = "parameter_bool"; bool param_value = true; @@ -150,7 +151,7 @@ if (RCL_RET_OK != rc) { } ``` -- Integer parameter +- Integer: ```c const char * parameter_name = "parameter_int"; int param_value = 100; @@ -165,7 +166,7 @@ rc = rclc_parameter_set_int(¶m_server, parameter_name, param_value); rc = rclc_parameter_get_int(¶m_server, parameter_name, ¶m_value); ``` -- Double parameter +- Double: ```c const char * parameter_name = "parameter_double"; double param_value = 0.15; diff --git a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md index 2b34d6b5..accf5763 100644 --- a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md +++ b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md @@ -195,8 +195,11 @@ For a detail on the available QoS options and the advantages and disadvantages b The executor is responsible to call the configured callback when a message is published. The function will have the message as its only argument, containing the values sent by the publisher: -// TODO: explain function prototype? ```c +// Function prototype: +void (* rclc_subscription_callback_t)(const void *); + +// Example: void subscription_callback(const void * msgin) { // Cast received message to used type diff --git a/_docs/tutorials/programming_rcl_rclc/service/services.md b/_docs/tutorials/programming_rcl_rclc/service/services.md index 581b1e48..8583d602 100644 --- a/_docs/tutorials/programming_rcl_rclc/service/services.md +++ b/_docs/tutorials/programming_rcl_rclc/service/services.md @@ -110,6 +110,10 @@ int64 sum The client request message will contain two integers `a` and `b`, and expects the `sum` of them as a response: ```c +// Function prototype: +void (* rclc_service_callback_t)(const void *, void *); + +// Example: void service_callback(const void * request_msg, void * response_msg){ // Cast messages to expected types example_interfaces__srv__AddTwoInts_Request * req_in = @@ -226,6 +230,10 @@ The function will have the response message as its only argument, containing the It is necessary to cast the response message to the expected type. Example: ```c +// Function prototype: +void (* rclc_client_callback_t)(const void *); + +// Example: void client_callback(const void * response_msg){ // Cast response message to expected type example_interfaces__srv__AddTwoInts_Response * msgin = @@ -263,7 +271,6 @@ Following the example on `AddTwoInts.srv`: example_interfaces__srv__AddTwoInts_Request request_msg; // Initialize request message memory and set its values -// TODO: use custom API? example_interfaces__srv__AddTwoInts_Request__init(&request_msg); request_msg.a = 24; request_msg.b = 42; From d9df00b9b3afac86cd37431307d1cd00b09ccb6f Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Thu, 26 Aug 2021 11:10:58 +0200 Subject: [PATCH 12/14] Add continuos serialization --- .../programming_rcl_rclc/executor/executor.md | 1 - .../micro-ROS/micro-ROS.md | 65 +++++++++++++++++-- .../programming_rcl_rclc/pub_sub/pub_sub.md | 4 +- .../tutorials/programming_rcl_rclc/qos/QoS.md | 3 - .../programming_rcl_rclc/service/services.md | 4 +- 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/_docs/tutorials/programming_rcl_rclc/executor/executor.md b/_docs/tutorials/programming_rcl_rclc/executor/executor.md index d4e22472..6978e8fd 100644 --- a/_docs/tutorials/programming_rcl_rclc/executor/executor.md +++ b/_docs/tutorials/programming_rcl_rclc/executor/executor.md @@ -53,7 +53,6 @@ void timer_callback(rcl_timer_t * timer, int64_t last_call_time) During the callback the timer can be canceled or have its period and/or callback modified using the passed pointer. Check [rcl/timer.h](https://github.com/ros2/rcl/blob/galactic/rcl/include/rcl/timer.h) for details. - ### Cleaning Up To destroy an initialized timer: diff --git a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md index a0b2beac..baa8b148 100644 --- a/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md +++ b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md @@ -168,9 +168,62 @@ else ## Continous serialization --```c -void rmw_uros_set_continous_serialization_callbacks( -rmw_publisher_t * publisher, -rmw_uros_continous_serialization_size size_cb, -rmw_uros_continous_serialization serialization_cb); -``` \ No newline at end of file +This utility allows the client to serialize and send data up to a customized size. The user can set the topic lenght and then serialize the data within the publish process. An example can be found on [`micro-ROS-demos/rclc/ping_uros_agent`](https://github.com/micro-ROS/micro-ROS-demos/blob/galactic/rclc/ping_uros_agent/main.c), where fragments from an image are requested and serialized on the spot. + +The user needs to define two callbacks, then set them on the `rmw`. It is recommended to clean the callbacks after the publication, to avoid interferences with other topics on the same process: + +```c +// Set serialization callbacks +rmw_uros_set_continous_serialization_callbacks(size_cb, serialization_cb); + +// Publish message +rcl_publish(...); + +// Clean callbacks +rmw_uros_set_continous_serialization_callbacks(NULL, NULL); +``` + +- Size callback: + +This callback will pass a pointer with the calculated message size. The user is responsible of increase this size to the expected value: + +```c +// Function prototype: +void (* rmw_uros_continous_serialization_size)(uint32_t * topic_length); + +// Implementation example: +void size_cb(uint32_t * topic_length){ + // Increase message size + *topic_length += ucdr_alignment(*topic_length, sizeof(uint32_t)) + sizeof(uint32_t); + *topic_length += IMAGE_BYTES; +} +``` + +- Serialize callback: + +This callback gives the user the message buffer to be completed. The user is responsible of serialize the data up to the lenght established on the size callback: + +```c +// Function prototype: +void (* rmw_uros_continous_serialization)(ucdrBuffer * ucdr); + +// Implementation example: +void serialization_cb(ucdrBuffer * ucdr){ + size_t len = 0; + micro_ros_fragment_t fragment; + + // Serialize array size + ucdr_serialize_uint32_t(ucdr, IMAGE_BYTES); + + while(len < IMAGE_BYTES){ + // Wait for new image "fragment" + ... + + // Serialize data fragment + ucdr_serialize_array_uint8_t(ucdr, fragment.data, fragment.len); + len += fragment.len; + } +} +``` + +*Note: When the callback ends, the message will be published.* diff --git a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md index accf5763..e7348ef4 100644 --- a/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md +++ b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md @@ -199,7 +199,7 @@ The function will have the message as its only argument, containing the values s // Function prototype: void (* rclc_subscription_callback_t)(const void *); -// Example: +// Implementation example: void subscription_callback(const void * msgin) { // Cast received message to used type @@ -210,7 +210,6 @@ void subscription_callback(const void * msgin) } ``` - Once the subscriber and the executor are initialized, the subscriber callback must be added to the executor to receive incoming publications once its spinning: ```c @@ -225,6 +224,7 @@ rcl_ret_t rc = rclc_executor_add_subscription( if (RCL_RET_OK != rc) { ... // Handle error return -1; +} // Spin executor to receive messages rclc_executor_spin(&executor); diff --git a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md index c4c2cf60..e2bf648b 100644 --- a/_docs/tutorials/programming_rcl_rclc/qos/QoS.md +++ b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md @@ -3,9 +3,6 @@ title: Quality of service permalink: /docs/tutorials/programming_rcl_rclc/qos/ --- -// Add benchmark results for Throughput and RTT to compare both modes? -// Explain custom QoS options - - [Reliable QoS](#reliable-qos) - [Best Effort](#best-effort) - [Custom QoS configuration](#custom-qos-configuration) diff --git a/_docs/tutorials/programming_rcl_rclc/service/services.md b/_docs/tutorials/programming_rcl_rclc/service/services.md index 8583d602..c140c05b 100644 --- a/_docs/tutorials/programming_rcl_rclc/service/services.md +++ b/_docs/tutorials/programming_rcl_rclc/service/services.md @@ -113,7 +113,7 @@ The client request message will contain two integers `a` and `b`, and expects th // Function prototype: void (* rclc_service_callback_t)(const void *, void *); -// Example: +// Implementation example: void service_callback(const void * request_msg, void * response_msg){ // Cast messages to expected types example_interfaces__srv__AddTwoInts_Request * req_in = @@ -233,7 +233,7 @@ It is necessary to cast the response message to the expected type. Example: // Function prototype: void (* rclc_client_callback_t)(const void *); -// Example: +// Implementation example: void client_callback(const void * response_msg){ // Cast response message to expected type example_interfaces__srv__AddTwoInts_Response * msgin = From 00f73346f184b927a7c4e9b4f858f6d94d5ad529 Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Fri, 27 Aug 2021 09:59:27 +0200 Subject: [PATCH 13/14] Minor change --- _docs/tutorials/programming_rcl_rclc/overview/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_docs/tutorials/programming_rcl_rclc/overview/index.md b/_docs/tutorials/programming_rcl_rclc/overview/index.md index 2371b80c..43571f8b 100644 --- a/_docs/tutorials/programming_rcl_rclc/overview/index.md +++ b/_docs/tutorials/programming_rcl_rclc/overview/index.md @@ -11,7 +11,7 @@ In this section, you'll learn the basics of the micro-ROS C API: **rclc**. The major concepts (publishers, subscriptions, services, timers, ...) are identical with ROS 2. They even rely on the *same* implementation, as the micro-ROS C API is based on the ROS 2 client support library (rcl), enriched with a set of convenience functions by the package [rclc](https://github.com/ros2/rclc/). That is, rclc does not add a new layer of types on top of rcl (like rclcpp and rclpy do) but only provides functions that ease the programming with the rcl types. New types are introduced only for concepts that are missing in rcl, such as the concept of an executor. * [**Nodes**](../node/) -* [**Publishers and Subscriptions**](../pub_sub/) +* [**Publishers and subscribers**](../pub_sub/) * [**Services**](../service/) * [**Parameters**](../parameters/) * [**Executor and timers**](../executor/) From a770368fbb7357da1e00b894664fd30c3b958f67 Mon Sep 17 00:00:00 2001 From: Antonio cuadros Date: Fri, 27 Aug 2021 10:05:45 +0200 Subject: [PATCH 14/14] Fix ident --- _docs/tutorials/programming_rcl_rclc/overview/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/_docs/tutorials/programming_rcl_rclc/overview/index.md b/_docs/tutorials/programming_rcl_rclc/overview/index.md index 43571f8b..14e2f987 100644 --- a/_docs/tutorials/programming_rcl_rclc/overview/index.md +++ b/_docs/tutorials/programming_rcl_rclc/overview/index.md @@ -6,6 +6,7 @@ redirect_from: --- + In this section, you'll learn the basics of the micro-ROS C API: **rclc**. The major concepts (publishers, subscriptions, services, timers, ...) are identical with ROS 2. They even rely on the *same* implementation, as the micro-ROS C API is based on the ROS 2 client support library (rcl), enriched with a set of convenience functions by the package [rclc](https://github.com/ros2/rclc/). That is, rclc does not add a new layer of types on top of rcl (like rclcpp and rclpy do) but only provides functions that ease the programming with the rcl types. New types are introduced only for concepts that are missing in rcl, such as the concept of an executor.