diff --git a/_data/docs.yml b/_data/docs.yml index 19bad0a5..91133c78 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,17 @@ - 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/executor + - 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 664c4e11..00000000 Binary files a/_docs/tutorials/core/programming_rcl_rclc/doc/highPriorityPath.png and /dev/null differ diff --git a/_docs/tutorials/core/programming_rcl_rclc/doc/scheduling_01.png b/_docs/tutorials/core/programming_rcl_rclc/doc/scheduling_01.png deleted file mode 100644 index d0b752af..00000000 Binary files a/_docs/tutorials/core/programming_rcl_rclc/doc/scheduling_01.png and /dev/null differ 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 c30783a7..00000000 Binary files a/_docs/tutorials/core/programming_rcl_rclc/doc/scheduling_02.png and /dev/null differ diff --git a/_docs/tutorials/core/programming_rcl_rclc/doc/scheduling_LET.png b/_docs/tutorials/core/programming_rcl_rclc/doc/scheduling_LET.png deleted file mode 100644 index 77860972..00000000 Binary files a/_docs/tutorials/core/programming_rcl_rclc/doc/scheduling_LET.png and /dev/null differ diff --git a/_docs/tutorials/core/programming_rcl_rclc/doc/sensePlanActScheme.png b/_docs/tutorials/core/programming_rcl_rclc/doc/sensePlanActScheme.png deleted file mode 100644 index 4487ebb6..00000000 Binary files a/_docs/tutorials/core/programming_rcl_rclc/doc/sensePlanActScheme.png and /dev/null differ diff --git a/_docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_01.png b/_docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_01.png deleted file mode 100644 index f692d25f..00000000 Binary files a/_docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_01.png and /dev/null differ diff --git a/_docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_02.png b/_docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_02.png deleted file mode 100644 index 8ef6df65..00000000 Binary files a/_docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_02.png and /dev/null differ 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 d0615c1b..00000000 Binary files a/_docs/tutorials/core/programming_rcl_rclc/doc/sensorFusion_03.png and /dev/null differ diff --git a/_docs/tutorials/core/programming_rcl_rclc/index.md b/_docs/tutorials/programming_rcl_rclc/executor/executor.md similarity index 71% rename from _docs/tutorials/core/programming_rcl_rclc/index.md rename to _docs/tutorials/programming_rcl_rclc/executor/executor.md index 8d5ff504..6978e8fd 100644 --- a/_docs/tutorials/core/programming_rcl_rclc/index.md +++ b/_docs/tutorials/programming_rcl_rclc/executor/executor.md @@ -1,313 +1,80 @@ --- -title: Programming with rcl and rclc -permalink: /docs/tutorials/core/programming_rcl_rclc/ +title: Executor and timers +permalink: /docs/tutorials/programming_rcl_rclc/executor/ --- - +- [Timers](#timers) + - [Initialization](#initialization) + - [Callback](#callback) + - [Cleaning Up](#cleaning-up) +- [Executor](#executor) + - [Example 1: 'Hello World'](#example-1-hello-world) + - [Example 2: Triggered execution](#example-2-triggered-execution) -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. +## Timers -* [Creating a node](#node) -* [Publishers and subscriptions](#pub_sub) -* [Services](#services) -* [Timers](#timers) -* [Lifecycle](#lifecycle) -* [Rclc Executor](#rclc_executor) +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. -## 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); - } -} -``` +### Initialization + +```c +// Timer period on nanoseconds +const unsigned int timer_period = RCL_MS_TO_NS(1000); -## Timers +// Create and initialize timer object +rcl_timer_t timer; +rcl_ret_t rc = rclc_timer_init_default(&timer, &support, timer_period, timer_callback); -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): +// Add to the executor +rc = rclc_executor_add_timer(&executor, &timer); -```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"); + ... // Handle error 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; +### Callback -// 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); +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. -// 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 +void timer_callback(rcl_timer_t * timer, int64_t last_call_time) +{ + printf("Last callback time: %ld\n", last_call_time); -```C -// declare callback -rcl_ret_t my_on_configure() { - printf(" >>> my_lifecycle_node: on_configure() callback called.\n"); - return RCL_RET_OK; + if (timer != NULL) { + // Perform actions + ... + } } -... - -// 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. +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 clean everything up, simply do +To destroy an initialized timer: -```C -rc += rcl_lifecycle_node_fini(&my_lifecycle_node, &allocator); +```c +// Destroy timer +rcl_timer_fini(&timer); ``` -### 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)). +This will deallocate used memory and make the timer invalid + -## rclc Executor +## 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: +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 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. +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' @@ -315,7 +82,7 @@ To start with, we provide a very simple example for an rclc Executor with one ti 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 +```c #include #include #include @@ -324,7 +91,7 @@ First, you include some header files, in particular the [rclc/rclc.h](https://gi We define a publisher and two strings, which will be used later. -```C +```c rcl_publisher_t my_pub; std_msgs__msg__String pub_msg; std_msgs__msg__String sub_msg; @@ -332,7 +99,7 @@ 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 +```c void my_subscriber_callback(const void * msgin) { const std_msgs__msg__String * msg = (const std_msgs__msg__String *)msgin; @@ -346,7 +113,7 @@ void my_subscriber_callback(const void * msgin) The timer callback publishes the message `pub_msg` with the publisher `my_pub`, which is initialized later in `main()`. -```C +```c void my_timer_callback(rcl_timer_t * timer, int64_t last_call_time) { rcl_ret_t rc; @@ -366,7 +133,7 @@ void my_timer_callback(rcl_timer_t * timer, int64_t last_call_time) 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 +```c int main(int argc, const char * argv[]) { rcl_allocator_t allocator = rcl_get_default_allocator(); @@ -383,7 +150,7 @@ int main(int argc, const char * argv[]) Next, you define a ROS 2 node `my_node` and initialize it with `rclc_executor_init_default()`: -```C +```c // create rcl_node rcl_node_t my_node; rc = rclc_node_init_default(&my_node, "node_0", "executor_examples", &support); @@ -395,7 +162,7 @@ Next, you define a ROS 2 node `my_node` and initialize it with `rclc_executor_in You can create a publisher to publish topic 'topic_0' with type std_msg::msg::String with the following code: -```C +```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); @@ -411,7 +178,7 @@ Note, that variable `my_pub` was defined globally, so it can be used by the time You can create a timer `my_timer` with a period of one second, which executes the callback `my_timer_callback` like this: -```C +```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); @@ -425,7 +192,7 @@ You can create a timer `my_timer` with a period of one second, which executes th 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 +```c // assign message to publisher std_msgs__msg__String__init(&pub_msg); const unsigned int PUB_MSG_CAPACITY = 20; @@ -437,7 +204,7 @@ The string `Hello World!` can be assigned directly to the message of the publish A subscription `my_sub`can be defined like this: -```C +```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) { @@ -450,20 +217,20 @@ A subscription `my_sub`can be defined like this: The global message for this subscription `sub_msg` needs to be initialized with: -```C +```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 +```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 +```c // total number of handles = #subscriptions + #timers unsigned int num_handles = 1 + 1; rclc_executor_init(&executor, &support.context, num_handles, &allocator); @@ -471,7 +238,7 @@ In the next step, executor is initialized with the ROS 2 `context`, the number o 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 +```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"); @@ -484,7 +251,7 @@ Note: Another execution semantics is `ALWAYS`, which means, that the subscriptio Likewise, you can add the timer `my_timer` with the function `rclc_c_executor_add_timer`: -```C +```c rclc_executor_add_timer(&executor, &my_timer); if (rc != RCL_RET_OK) { printf("Error in rclc_executor_add_timer.\n"); @@ -497,13 +264,13 @@ In this example, the timer was added to the executor before the subscription. Th Finally, you can run the executor with `rclc_executor_spin()`: -```C +```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 +```c for (unsigned int i = 0; i < 10; i++) { // timeout specified in nanoseconds (here 1s) rclc_executor_spin_some(&executor, 1000 * (1000 * 1000)); @@ -512,7 +279,7 @@ for (unsigned int i = 0; i < 10; i++) { At the end, you need to free dynamically allocated memory: -```C +```c // clean up rc = rclc_executor_fini(&executor); rc += rcl_publisher_fini(&my_pub, &my_node); @@ -533,7 +300,7 @@ return 0; 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 +### 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. @@ -543,7 +310,7 @@ We setup one executor with two publishers, one with 100ms and one with 1000ms pe The output of this code example will look like this: -```C +```c Created timer 'my_string_timer' with timeout 100 ms. Created 'my_int_timer' with timeout 1000 ms. Created subscriber topic_0: @@ -589,7 +356,7 @@ You learn in this tutorial 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 +```c #include #include #include @@ -601,7 +368,7 @@ We start with the necessary includes for string and int messages, `data.data`: -```C +```c void my_string_subscriber_callback(const void * msgin) { const std_msgs__msg__String * msg = (const std_msgs__msg__String *)msgin; @@ -714,7 +481,7 @@ void my_string_subscriber_callback(const void * msgin) The integer callback prints out the received integer `msg->data`: -```C +```c void my_int_subscriber_callback(const void * msgin) { const std_msgs__msg__Int32 * msg = (const std_msgs__msg__Int32 *)msgin; @@ -733,7 +500,7 @@ In the `my_timer_string_callback`, the message `pub_msg` is created and filled w The macro `UNUSED` is a workaround for the linter warning, that the second parameter `last_call_time` is not used. -```C +```c #define UNUSED(x) (void)x; void my_timer_string_callback(rcl_timer_t * timer, int64_t last_call_time) @@ -766,7 +533,7 @@ void my_timer_string_callback(rcl_timer_t * timer, int64_t last_call_time) 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 +```c void my_timer_int_callback(rcl_timer_t * timer, int64_t last_call_time) { rcl_ret_t rc; @@ -788,7 +555,7 @@ void my_timer_int_callback(rcl_timer_t * timer, int64_t last_call_time) Now were are all set for the `main()` function: -```C +```c int main(int argc, const char * argv[]) { rcl_allocator_t allocator = rcl_get_default_allocator(); @@ -805,7 +572,7 @@ int main(int argc, const char * argv[]) 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 +```c // create rcl_node rcl_node_t my_node; rc = rclc_node_init_default(&my_node, "node_0", "executor_examples", &support); @@ -817,7 +584,7 @@ First rcl is initialized with the `rclc_support_init` using the default `allocat 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 +```c // create a publisher 1 // - topic name: 'topic_0' // - message type: std_msg::msg::String @@ -846,7 +613,7 @@ if (rc != RCL_RET_OK) { 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 +```c // create publisher 2 // - topic name: 'topic_1' // - message type: std_msg::msg::Int @@ -874,7 +641,7 @@ Likewise, a second publisher `my_int_pub, which publishes an int message and its 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 +```c std_msgs__msg__Int32__init(&int_pub_msg); int_pub_value = 0; string_pub_value = 0; @@ -882,7 +649,7 @@ 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 +```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(); @@ -901,7 +668,7 @@ The first subscription `my_string_sub` is created with the function `rcl_subscri 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 +```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); @@ -917,14 +684,14 @@ The second subscription `my_int_sub` is created with the rclc convenience functi In this example, we are using two executors, one to schedule the publishers, and one to schedule the subscriptions: -```C +```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 +```c // Executor for publishing messages unsigned int num_handles_pub = 2; printf("Executor_pub: number of DDS handles: %u\n", num_handles_pub); @@ -944,7 +711,7 @@ The executor `executor_pub` is first created with `rclc_executor_get_zero_initi Both timers are added to the exececutor with the function `rclc_executor_add_timer`: -```C +```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"); @@ -958,7 +725,7 @@ if (rc != RCL_RET_OK) { Also the second publisher has two handles, the two subscriptions: -```C +```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(); @@ -967,7 +734,7 @@ rclc_executor_init(&executor_sub, &support.context, num_handles_sub, &allocator) Which are added with the function `rclc_executor_add_subscription`: -```C +```c // add subscription to executor rc = rclc_executor_add_subscription( &executor_sub, &my_string_sub, &string_sub_msg, @@ -990,14 +757,14 @@ if (rc != RCL_RET_OK) { 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 +```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 +```c for (unsigned int i = 0; i < 100; i++) { // timeout specified in ns (here: 1s) rclc_executor_spin_some(&executor_pub, 1000 * (1000 * 1000)); @@ -1008,7 +775,7 @@ for (unsigned int i = 0; i < 100; i++) { This example is concluded with the clean-up code: -```C +```c // clean up rc = rclc_executor_fini(&executor_pub); rc += rclc_executor_fini(&executor_sub); @@ -1037,7 +804,7 @@ In case the default trigger conditions are not sufficient, then the user can def The source code of the custom-programmed trigger condition has already been presented. The following code will setup the executor accordingly: -```C +```c pub_trigger_object_t comm_obj_pub; comm_obj_pub.timer1 = &my_string_timer; comm_obj_pub.timer2 = &my_int_timer; @@ -1053,3 +820,5 @@ The following code will setup the executor accordingly: 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..baa8b148 --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/micro-ROS/micro-ROS.md @@ -0,0 +1,229 @@ +--- +title: micro-ROS utilities +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). + +```c +// 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 +// 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 + ... +} +``` + +*Note: `rmw_uros_ping_agent` is thread safe.* + +## Continous serialization + +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/node/node.md b/_docs/tutorials/programming_rcl_rclc/node/node.md new file mode 100644 index 00000000..00a4fe40 --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/node/node.md @@ -0,0 +1,203 @@ +--- +title: Nodes +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) + + +- [Initialization](#initialization) + - [Cleaning Up](#cleaning-up) +- [Lifecycle](#lifecycle) + - [Initialization](#initialization-1) + - [Callbacks](#callbacks) + - [Running](#running) + - [Cleaning Up](#cleaning-up-1) + - [Limitations](#limitations) + +## Initialization + +- Create a node with default configuration: + ```c + // Initialize micro-ROS allocator + rcl_allocator_t allocator = rcl_get_default_allocator(); + + // 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"; + + // Node namespace (Can remain empty "") + const char * namespace = "test_namespace"; + + // Init default node + rc = rclc_node_init_default(&node, node_name, namespace, &support); + if (rc != RCL_RET_OK) { + ... // Handle error + return -1; + } + ``` + +- Create a node with custom 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 + // 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); + + 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 + // 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, ...) have to be destroyed before the node itself: + +```c +// Destroy created entities (Example) +rcl_publisher_fini(&publisher, &node); +... + +// Destroy the node +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/overview/index.md b/_docs/tutorials/programming_rcl_rclc/overview/index.md new file mode 100644 index 00000000..14e2f987 --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/overview/index.md @@ -0,0 +1,20 @@ +--- +title: Overview +permalink: /docs/tutorials/programming_rcl_rclc/overview/ +redirect_from: + - /docs/tutorials/programming_rcl_rclc/ +--- + + + +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 subscribers**](../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 new file mode 100644 index 00000000..18438a2e --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/parameters/parameters.md @@ -0,0 +1,193 @@ +--- +title: Parameter server +permalink: /docs/tutorials/programming_rcl_rclc/parameters/ +--- + +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. + +Note: micro-ROS parameter server is only supported on ROS2 galactic distribution + +- [Initialization](#initialization) +- [Memory requirements](#memory-requirements) +- [Callback](#callback) +- [Add a parameter](#add-a-parameter) +- [Cleaning up](#cleaning-up) + +## Initialization + +- Default initialization: + ```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) { + ... // Handle error + return -1; + } + ``` + +- 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; + + // Initialize 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 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: + +```json +# 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" + ] + } + } +} +``` + +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 + +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 +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"); +} +``` +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); +``` + +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 + +micro-ROS parameter server supports the following parameter types: + +- Boolean: +```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: +```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: +```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); +``` + +## Cleaning up + +To destroy an initialized parameter server: + +```c +// Delete parameter server +rclc_parameter_server_fini(¶m_server, &node); +``` + +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 new file mode 100644 index 00000000..e7348ef4 --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/pub_sub/pub_sub.md @@ -0,0 +1,250 @@ +--- +title: Publishers and Subscribers +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 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. + +- [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 + +### 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 + // 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 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; + } + ``` + +- Best effort: + ```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 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) { + ... // Handle error + return -1; + } + ``` + +- Custom QoS: + + ```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 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) { + ... // 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 + +To publish messages to the topic: + +```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; +} +``` + +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 + +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); + + // Initialize a reliable subscriber + 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; + } + ``` + +- Custom QoS: + + ```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 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) { + ... // 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/). + +### 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: + +```c +// Function prototype: +void (* rclc_subscription_callback_t)(const void *); + +// Implementation example: +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 its spinning: + +```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); + +if (RCL_RET_OK != rc) { + ... // Handle error + return -1; +} + +// Spin executor to receive messages +rclc_executor_spin(&executor); +``` + +## 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 + +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 +rcl_publisher_fini(&publisher, &node); + +// Destroy subscriber +rcl_subscription_fini(&subscriber, &node); +``` + +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 new file mode 100644 index 00000000..e2bf648b --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/qos/QoS.md @@ -0,0 +1,68 @@ +--- +title: Quality of service +permalink: /docs/tutorials/programming_rcl_rclc/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 + // 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 sent are expected to be received. This method improves publication throughput and reduces resources usage but is vulnerable to communication errors. + +## 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 +{ + 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. + bool avoid_ros_namespace_conventions; +} rmw_qos_profile_t; +``` 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..c140c05b --- /dev/null +++ b/_docs/tutorials/programming_rcl_rclc/service/services.md @@ -0,0 +1,302 @@ +--- +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 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](#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 + // 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; + } + ``` + +- 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; + } + ``` + +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 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 +int64 a +int64 b +--- +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 *); + +// Implementation example: +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 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 +// 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); + +if (rc != RCL_RET_OK) { + ... // Handle error + return -1; +} + +// Spin executor to receive requests +rclc_executor_spin(&executor); +``` + +## 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; + } + ``` + +- 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 sent by the server. + +It is necessary to cast the response message to the expected type. Example: +```c +// Function prototype: +void (* rclc_client_callback_t)(const void *); + +// Implementation example: +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); +``` + +## 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 + +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. 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 %}