|
| 1 | +--- |
| 2 | +title: Handling messages memory in micro-ROS |
| 3 | +permalink: /docs/tutorials/advanced/handling_type_memory/ |
| 4 | +--- |
| 5 | + |
| 6 | +This page aims to explain how to handle messages and types memory in micro-ROS. |
| 7 | + |
| 8 | +First of all, since the micro-ROS user is in an embedded C99 environment, it is important to be aware of what messages and ROS 2 types are being used in order to handle memory correctly. |
| 9 | + |
| 10 | +By watching the `.msg` or `.srv` of the types used in a micro-ROS application, you can determine the type of each member. Currently, the following types are supported: |
| 11 | +- Basic type |
| 12 | +- Array type |
| 13 | +- Sequence type |
| 14 | +- Compound type |
| 15 | + |
| 16 | +Let's take an example `.mgs` for clarification: |
| 17 | + |
| 18 | +``` |
| 19 | +# MyType.msg |
| 20 | +std_msgs/Header header |
| 21 | +int32[] values |
| 22 | +float64 duration |
| 23 | +int8[10] coefficients |
| 24 | +string name |
| 25 | +``` |
| 26 | + |
| 27 | +In this example: |
| 28 | +- the member `duration` is a **basic type member**,. |
| 29 | +- the member `values` is a **sequence type member** because it has a unbounded sequence of `int32`, in this case. |
| 30 | +- the member `coefficients` is an **array type member** because it has a bounded sequence of 10 units of `int8`, in this case. |
| 31 | +- the member `header` is an **compound type member** because it refers to complex type described in the same or other ROS 2 package. |
| 32 | +- the member `name` is an **string type member** and should be understood as a `char[]` (sequence type member). |
| 33 | + |
| 34 | +When dealing with the **micro-ROS typesupport** the developer needs to take into account how this message is going to be handled in the C99 API of micro-ROS. In general, the micro-ROS typesupport will create a C99 struct representation of the message: |
| 35 | + |
| 36 | +```c |
| 37 | +typedef struct mypackage__msg__MyType |
| 38 | +{ |
| 39 | + std_msgs__msg__Header header; |
| 40 | + rosidl_runtime_c__int32__Sequence values; |
| 41 | + double duration; |
| 42 | + int8 coefficients[10]; |
| 43 | + rosidl_runtime_c__String name; // equal to rosidl_runtime_c__char__Sequence |
| 44 | +} mypackage__msg__MyType; |
| 45 | +``` |
| 46 | + |
| 47 | +So when in an application has a variable of this type, for example `mypackage__msg__MyType mymsg;`, we know that: |
| 48 | +- `mymsg.coefficients` has a C array of `int8` |
| 49 | +- `mymsg.duration` is a `double` member |
| 50 | + |
| 51 | +but, what happens with the `...Sequence` and the compound type member? |
| 52 | + |
| 53 | +## Sequence types in micro-ROS |
| 54 | + |
| 55 | +A **sequence type member** is an especial type member that hosts a pointer `data`, a `size` and a `capacity` value. The pointer should have memory for storing up to `capacity` values and `size` member shows how many element are currently in the sequence. Usually in micro-ROS, the user is in charge of assigning memory and values to this sequence members. |
| 56 | + |
| 57 | +In the case of `MyType.msg`, the `values` sequence member is represented in C99 as this struct: |
| 58 | + |
| 59 | +```c |
| 60 | +typedef struct rosidl_runtime_c__int32__Sequence |
| 61 | +{ |
| 62 | + int32_t* data; /* The pointer to an array of int32 */ |
| 63 | + size_t size; /* The number of valid items in data */ |
| 64 | + size_t capacity; /* The number of allocated items in data */ |
| 65 | +} rosidl_runtime_c__int32__Sequence; |
| 66 | +``` |
| 67 | + |
| 68 | +So user need to handle the type like: |
| 69 | + |
| 70 | +```c |
| 71 | +mypackage__msg__MyType mymsg; |
| 72 | + |
| 73 | +// mymsg.values.data is NULL or garbage now |
| 74 | +// mymsg.values.size is 0 or garbage now |
| 75 | +// mymsg.values.capacity is 0 or garbage now |
| 76 | + |
| 77 | +// Assigning dynamic memory to the sequence |
| 78 | +mymsg.values.capacity = 100; |
| 79 | +mymsg.values.data = (int32_t*) malloc(mymsg.values.capacity * sizeof(int32_t)); |
| 80 | +mymsg.values.size = 0; |
| 81 | + |
| 82 | +// Assigning static memory to the sequence |
| 83 | +static int32_t memory[100]; |
| 84 | +mymsg.values.capacity = 100; |
| 85 | +mymsg.values.data = memory; |
| 86 | +mymsg.values.size = 0; |
| 87 | + |
| 88 | +// Filling some data |
| 89 | +for(int32_t i = 0; i < 3; i++){ |
| 90 | + mymsg.values.data = i; |
| 91 | + mymsg.values.size++; |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +## Compound types in micro-ROS |
| 96 | + |
| 97 | +When dealing with a compound type, the user should recursively inspect the types in order to determine how to handle each internal member. |
| 98 | + |
| 99 | +For example in the `MyType.msg` example, the `header` member has the following structure: |
| 100 | + |
| 101 | +```c |
| 102 | +typedef struct std_msgs__msg__Header |
| 103 | +{ |
| 104 | + builtin_interfaces__msg__Time stamp; |
| 105 | + rosidl_runtime_c__String frame_id; |
| 106 | +} std_msgs__msg__Header; |
| 107 | +``` |
| 108 | + |
| 109 | +Remember that `rosidl_runtime_c__String` is equivalent to `rosidl_runtime_c__char__Sequence`. And `builtin_interfaces__msg__Time` looks like: |
| 110 | + |
| 111 | +```c |
| 112 | +typedef struct builtin_interfaces__msg__Time |
| 113 | +{ |
| 114 | + int32_t sec; |
| 115 | + uint32_t nanosec; |
| 116 | +} builtin_interfaces__msg__Time; |
| 117 | +``` |
| 118 | + |
| 119 | +To initialize the `header` member of `MyType.msg`: |
| 120 | + |
| 121 | +```c |
| 122 | +mypackage__msg__MyType mymsg; |
| 123 | + |
| 124 | +// Assigning dynamic memory to the frame_id char sequence |
| 125 | +mymsg.header.frame_id.capacity = 100; |
| 126 | +mymsg.header.frame_id.data = (char*) malloc(mymsg.values.capacity * sizeof(char)); |
| 127 | +mymsg.header.frame_id.size = 0; |
| 128 | + |
| 129 | +// Assigning value to the frame_id char sequence |
| 130 | +strcpy(mymsg.header.frame_id.data, "Hello World"); |
| 131 | +mymsg.header.frame_id.size = strlen(mymsg.header.frame_id.data); |
| 132 | + |
| 133 | +// Assigning value to other members |
| 134 | +mymsg.stamp.sec = 10; |
| 135 | +mymsg.stamp.nanosec = 20; |
| 136 | +``` |
| 137 | +
|
| 138 | +## Sequences of compound types |
| 139 | +
|
| 140 | +Users should take into account that **sequence type member** of **compound type member** are also valid ROS 2 type. For example, let's modify the previous example: |
| 141 | +
|
| 142 | +``` |
| 143 | +# MyComplexType.msg |
| 144 | +std_msgs/Header[] multiheaders |
| 145 | +int32[] values |
| 146 | +float64 duration |
| 147 | +int8[10] coefficients |
| 148 | +string name |
| 149 | +``` |
| 150 | +
|
| 151 | +In this case, the generated typesupport will be: |
| 152 | +
|
| 153 | +```c |
| 154 | +typedef struct mypackage__msg__MyComplexType |
| 155 | +{ |
| 156 | + std_msgs__msg__Header__Sequence multiheaders; |
| 157 | + rosidl_runtime_c__int32__Sequence values; |
| 158 | + double duration; |
| 159 | + int8 coefficients[10]; |
| 160 | + rosidl_runtime_c__String name; // equal to rosidl_runtime_c__char__Sequence |
| 161 | +} mypackage__msg__MyComplexType; |
| 162 | +``` |
| 163 | + |
| 164 | +Notice that `multiheaders` is a **sequence type member**, so it should be handled properly, but also it is a **compound type member** which needs to be handled recursively, initializing its own members. For example: |
| 165 | + |
| 166 | +```c |
| 167 | +mypackage__msg__MyComplexType mymsg; |
| 168 | + |
| 169 | +// Init the multiheaders sequence |
| 170 | +mymsg.multiheaders.capacity = 10; |
| 171 | +mymsg.multiheaders.data = (std_msgs__msg__Header*) malloc(mymsg.values.capacity * sizeof(std_msgs__msg__Header)); |
| 172 | +mymsg.multiheaders.size = 0; |
| 173 | + |
| 174 | +// Filling some data |
| 175 | +for(int32_t i = 0; i < 3; i++){ |
| 176 | + mymsg.values.data = i; |
| 177 | + |
| 178 | + // Add memory to this sequence element frame_id |
| 179 | + mymsg.multiheaders.data[i].frame_id.capacity = 100; |
| 180 | + mymsg.multiheaders.data[i].frame_id.data = (char*) malloc(mymsg.multiheaders.data[i].frame_id.capacity * sizeof(char)); |
| 181 | + mymsg.multiheaders.data[i].frame_id.size = 0; |
| 182 | + |
| 183 | + // Assigning value to the frame_id char sequence |
| 184 | + strcpy(mymsg.multiheaders.data[i].frame_id.data, "Hello World"); |
| 185 | + mymsg.multiheaders.data[i].frame_id.size = strlen(mymsg.multiheaders.data[i].frame_id.data); |
| 186 | + |
| 187 | + // Assigning value to other members |
| 188 | + mymsg.multiheaders.data[i].stamp.sec = 10; |
| 189 | + mymsg.multiheaders.data[i].stamp.nanosec = 20; |
| 190 | + |
| 191 | + mymsg.multiheaders.size++; |
| 192 | +} |
| 193 | +``` |
0 commit comments