Skip to content

Commit d1e4dfb

Browse files
committed
working on 2nd rclc executor tutorial (with trigger)
1 parent 9240ced commit d1e4dfb

1 file changed

Lines changed: 136 additions & 6 deletions

File tree

  • _docs/tutorials/core/programming_rcl_rclc

‎_docs/tutorials/core/programming_rcl_rclc/index.md‎

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -380,15 +380,15 @@ if (rc != RCL_RET_OK) {
380380
printf("Error in rclc_executor_add_timer.\n");
381381
}
382382
```
383-
A key feature of the rclc-Executor is, that the order of these `rclc-executor-add-* `-functions matters. It defines static processing order of the callbacks when the spin-function is called.
383+
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.
384384
385-
In this example, if the timer is ready and also a new message for the subscription is available, then the timer is executed first and then the subscription. Such a behavior cannot be defined currently with the rclcpp Executor.
385+
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.
386386
387-
The easiest way to run the Executor is to call `rclc_executor_spin()`:
387+
Finally, you can run the executor with `rclc_executor_spin()`:
388388
```C
389389
rclc_executor_spin(&executor);
390390
```
391-
This function runs forever without coming back. In this example we want to publish the message only ten times. Therefore we are using another spin-method `rclc_executor_spin_some`, which spins once and returns. The timeout for waiting for new messages is one second
391+
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.
392392
```C
393393
for (unsigned int i = 0; i < 10; i++) {
394394
// timeout specified in nanoseconds (here 1s)
@@ -417,7 +417,7 @@ return 0;
417417
} // main
418418
```
419419
420-
This completes the example. The source code can be found in [rclc-examples/example_executor_convenience.c](https://github.com/micro-ROS/rclc-examples/example_executor_convenience.c).
420+
This completes the example. The source code can be found in the package rclc-examples [rclc-examples/example_executor_convenience.c](https://github.com/micro-ROS/rclc-examples/example_executor_convenience.c).
421421
422422
#### Example 2: Triggered rclc_executor
423423
Sesor fusion is the first step in robotic applications when multiple sensor 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.
@@ -463,7 +463,7 @@ typedef struct
463463
rcl_subscription_t * sub2;
464464
} sub_trigger_object_t;
465465
```
466-
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.
466+
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.
467467

468468
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.
469469
```C
@@ -499,6 +499,136 @@ bool pub_trigger(rclc_executor_handle_t * handles, unsigned int size, void * obj
499499
}
500500
}
501501
```
502+
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.
503+
504+
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.
505+
506+
```C
507+
bool sub_trigger(rclc_executor_handle_t * handles, unsigned int size, void * obj)
508+
{
509+
if (handles == NULL) {
510+
printf("Error in sub_trigger: 'handles' is a NULL pointer\n");
511+
return false;
512+
}
513+
if (obj == NULL) {
514+
printf("Error in sub_trigger: 'obj' is a NULL pointer\n");
515+
return false;
516+
}
517+
sub_trigger_object_t * comm_obj = (sub_trigger_object_t *) obj;
518+
bool sub1 = false;
519+
bool sub2 = false;
520+
//printf("sub_trigger ready set: ");
521+
for (unsigned int i = 0; i < size; i++) {
522+
if (handles[i].data_available == true) {
523+
void * handle_ptr = rclc_executor_handle_get_ptr(&handles[i]);
524+
525+
if (handle_ptr == comm_obj->sub1) {
526+
sub1 = true;
527+
}
528+
if (handle_ptr == comm_obj->sub2) {
529+
sub2 = true;
530+
}
531+
}
532+
}
533+
if (sub1 && sub2) {
534+
return true;
535+
} else {
536+
return false;
537+
}
538+
}
539+
```
540+
Like in the HelloWorld-example, the subscription callbacks just prints out the received message.
541+
542+
The `my_string_subscriber` callback prints out the string of the message `msg->data.data`:
543+
```C
544+
void my_string_subscriber_callback(const void * msgin)
545+
{
546+
const std_msgs__msg__String * msg = (const std_msgs__msg__String *)msgin;
547+
if (msg == NULL) {
548+
printf("my_string_subscriber_callback: msgin is NULL\n");
549+
} else {
550+
printf("Callback 1: %s\n", msg->data.data);
551+
}
552+
}
553+
```
554+
555+
The integer callback prints out the received integer `msg->data`:
556+
```C
557+
void my_int_subscriber_callback(const void * msgin)
558+
{
559+
const std_msgs__msg__Int32 * msg = (const std_msgs__msg__Int32 *)msgin;
560+
if (msg == NULL) {
561+
printf("my_int_subscriber_callback: msgin is NULL\n");
562+
} else {
563+
printf("Callback 2: %d\n", msg->data);
564+
}
565+
}
566+
```
567+
To publish messages with different frequencies, we setup two timers.
568+
One timer to publish a string message, the `my_timer_string_callback` and one timer to publish the integer, the `my_timer_int_callback`.
569+
570+
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()`
571+
572+
The macro `UNUSED` is a workaround for the linter warning, that the second parameter `last_call_time` is not used.
573+
```C
574+
#define UNUSED(x) (void)x;
575+
void my_timer_string_callback(rcl_timer_t * timer, int64_t last_call_time)
576+
{
577+
rcl_ret_t rc;
578+
UNUSED(last_call_time);
579+
if (timer != NULL) {
580+
//printf("Timer: time since last call %d\n", (int) last_call_time);
581+
582+
std_msgs__msg__String pub_msg;
583+
std_msgs__msg__String__init(&pub_msg);
584+
const unsigned int PUB_MSG_CAPACITY = 20;
585+
pub_msg.data.data = malloc(PUB_MSG_CAPACITY);
586+
pub_msg.data.capacity = PUB_MSG_CAPACITY;
587+
snprintf(pub_msg.data.data, pub_msg.data.capacity, "Hello World!%d", pub_string_value++);
588+
pub_msg.data.size = strlen(pub_msg.data.data);
589+
590+
rc = rcl_publish(&my_pub, &pub_msg, NULL);
591+
if (rc == RCL_RET_OK) {
592+
printf("Published: %s\n", pub_msg.data.data);
593+
} else {
594+
printf("Error in my_timer_string_callback: publishing message %s\n", pub_msg.data.data);
595+
}
596+
std_msgs__msg__String__fini(&pub_msg);
597+
} else {
598+
printf("Error in my_timer_string_callback: timer parameter is NULL\n");
599+
}
600+
}
601+
```
602+
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()`
603+
```C
604+
void my_timer_int_callback(rcl_timer_t * timer, int64_t last_call_time)
605+
{
606+
rcl_ret_t rc;
607+
UNUSED(last_call_time);
608+
if (timer != NULL) {
609+
//printf("Timer: time since last call %d\n", (int) last_call_time);
610+
pub_int_msg.data = pub_int_value++;
611+
rc = rcl_publish(&my_int_pub, &pub_int_msg, NULL);
612+
if (rc == RCL_RET_OK) {
613+
printf("Published: %d\n", pub_int_msg.data);
614+
} else {
615+
printf("Error in my_timer_int_callback: publishing message %d\n", pub_int_msg.data);
616+
}
617+
} else {
618+
printf("Error in my_timer_int_callback: timer parameter is NULL\n");
619+
}
620+
}
621+
```
622+
623+
Now were are all set for the `main()` function:
624+
625+
626+
CONTINUE HERE:
627+
- change source code to use the rclc-convenience functions
628+
- later change also the name example_convenience to example_helloWorld
629+
- have everything complete? or only partial example - tuturial shall be complete!
630+
631+
502632

503633
The source code can be found in [rclc-examples/example_executor_trigger.c](https://github.com/micro-ROS/rclc-examples/example_executor_trigger.c).
504634

0 commit comments

Comments
 (0)