Skip to content

Commit 4d7eda5

Browse files
authored
Ref #8025. Adding Linux micro-ROS tutorial (#130)
* Initial tutorial Update Update * Update docs * Fix * Updates * Fix * Updates * Update index.md * Update cmake * Update user
1 parent a734b33 commit 4d7eda5

2 files changed

Lines changed: 393 additions & 0 deletions

File tree

_data/docs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,5 @@
7474

7575
- title: Advanced Tutorials with Linux
7676
docs:
77+
- tutorials/advanced/linux/linux_getting_started
7778
- tutorials/advanced/linux/tracing
Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
---
2+
title: Linux Getting Started
3+
permalink: /docs/tutorials/advanced/linux/linux_getting_started/
4+
---
5+
6+
This tutorial aims to create a new micro-ROS application on Linux for testing purposes.
7+
8+
## Adding a new micro-ROS app
9+
10+
First of all, make sure that you have a **ROS 2** installation.
11+
12+
***TIP:** if you are familiar with Docker containers, this image may be useful: [ros:dashing](https://hub.docker.com/layers/ros/library/ros/dashing/images/sha256-b796c14ea663537129897769aa6c715a851ca08dffd4875ef2ecaa31a4dbd431?context=explore)*
13+
14+
On the **ROS 2** installation open a command line and follow these steps:
15+
16+
```bash
17+
# Source the ROS 2 installation
18+
source /opt/ros/$ROS_DISTRO/setup.bash
19+
20+
# Create a workspace and download the micro-ROS tools
21+
mkdir microros_ws
22+
cd microros_ws
23+
git clone -b $ROS_DISTRO https://github.com/micro-ROS/micro-ros-build.git src/micro-ros-build
24+
25+
# Update dependencies using rosdep
26+
sudo apt update && rosdep update
27+
rosdep install --from-path src --ignore-src -y
28+
29+
# Build micro-ROS tools and source them
30+
colcon build
31+
source install/local_setup.bash
32+
```
33+
34+
Now, let's create a firmware workspace that targets all the required code and tools:
35+
36+
```bash
37+
# Create step
38+
ros2 run micro_ros_setup create_firmware_ws.sh host
39+
```
40+
41+
Now you have all the required tools to build micro-ROS. At this point, you must know that the micro-ROS build system is a four-step workflow:
42+
43+
<!-- TODO (pablogs9): Remove and link to build-system tutorial when done -->
44+
1. **Create**: retrieves all the required packages for a specific RTOS and hardware platform.
45+
2. **Configure**: configures the downloaded packages with options such as the micro-ROS application, the selected transport layer or the micro-ROS agent IP address (in network transports).
46+
3. **Build**: generates a binary file ready for being loaded in the hardware.
47+
4. **Flash**: load the micro-ROS software in the hardware.
48+
49+
micro-ROS apps for Linux are located at `src/uros/micro-ROS-demos/rcl/`. In order to create a new application, create a new folder containing two files: the app code and the CMake file.
50+
51+
```bash
52+
# Creating a new app
53+
pushd src/uros/micro-ROS-demos/rcl/
54+
mkdir my_brand_new_app
55+
cd my_brand_new_app
56+
touch app.c CMakeLists.txt
57+
popd
58+
```
59+
60+
For this example we are going to create a ping pong app where a node sends a ping package with a unique identifier using a publisher and the same package is received by a pong subscriber. The node will also answer to pings received from other nodes with a pong message:
61+
62+
![pingpong](http://www.plantuml.com/plantuml/png/ZOwnIWGn48RxFCNFzSkoUG2vqce5jHEHi1dtWZkPa6GByNntavZY10yknMJu-ORlFwPiOjvvK-d3-M2YOR1uMKvHc93ZJafvoMML07d7h1NAE-DPWblg_na8vnwEx9OeZmzFOt1-BK7AzetJciPxCfRYVw1S0SbRLBEg1IpXPIvpUWLCmZpXIm6BS3addt7uQpu0ZQlxT1MK2r0g-7sfqbsbRrVfMrMwgbev3CDTlsqJGtJhATUmSMrMg5TKwaZUxfcttuMt7m00)
63+
64+
To start creating this app, the `CMakeLists.txt` file should looks like:
65+
66+
```cmake
67+
cmake_minimum_required(VERSION 3.5)
68+
69+
project(my_brand_new_app LANGUAGES C)
70+
71+
find_package(ament_cmake REQUIRED)
72+
find_package(rcl REQUIRED)
73+
find_package(std_msgs REQUIRED)
74+
find_package(rmw_microxrcedds REQUIRED)
75+
76+
add_executable(${PROJECT_NAME} app.c)
77+
78+
ament_target_dependencies(${PROJECT_NAME}
79+
rcl
80+
std_msgs
81+
rmw_microxrcedds
82+
)
83+
84+
install(TARGETS ${PROJECT_NAME}
85+
DESTINATION ${PROJECT_NAME}
86+
)
87+
```
88+
89+
Meanwhile `app.c` should look like the following code:
90+
91+
```c
92+
#include <rcl/rcl.h>
93+
#include <rcl/error_handling.h>
94+
#include <std_msgs/msg/header.h>
95+
96+
#include <rmw_uros/options.h>
97+
98+
#include <stdio.h>
99+
#include <unistd.h>
100+
#include <pthread.h>
101+
102+
#define STRING_BUFFER_LEN 100
103+
104+
// Publishing ping guard condition thread
105+
void * trigger_guard_condition(void *args){
106+
rcl_guard_condition_t * guard_condition = (rcl_guard_condition_t *)args;
107+
108+
while(true){
109+
rcl_trigger_guard_condition(guard_condition);
110+
sleep(5);
111+
}
112+
}
113+
114+
int main(int argc, const char * const * argv)
115+
{
116+
//Init RCL options
117+
rcl_init_options_t options = rcl_get_zero_initialized_init_options();
118+
rcl_init_options_init(&options, rcl_get_default_allocator());
119+
120+
// Init RCL context
121+
rcl_context_t context = rcl_get_zero_initialized_context();
122+
rcl_init(0, NULL, &options, &context);
123+
124+
// Create a node
125+
rcl_node_options_t node_ops = rcl_node_get_default_options();
126+
rcl_node_t node = rcl_get_zero_initialized_node();
127+
rcl_node_init(&node, "pingpong_node", "", &context, &node_ops);
128+
129+
// Create a reliable ping publisher
130+
rcl_publisher_options_t ping_publisher_ops = rcl_publisher_get_default_options();
131+
rcl_publisher_t ping_publisher = rcl_get_zero_initialized_publisher();
132+
rcl_publisher_init(&ping_publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/ping", &ping_publisher_ops);
133+
134+
// Create a best effort pong publisher
135+
rcl_publisher_options_t pong_publisher_ops = rcl_publisher_get_default_options();
136+
pong_publisher_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
137+
rcl_publisher_t pong_publisher = rcl_get_zero_initialized_publisher();
138+
rcl_publisher_init(&pong_publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/pong", &pong_publisher_ops);
139+
140+
// Create a best effort pong subscriber
141+
rcl_subscription_options_t pong_subscription_ops = rcl_subscription_get_default_options();
142+
pong_subscription_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
143+
rcl_subscription_t pong_subscription = rcl_get_zero_initialized_subscription();
144+
rcl_subscription_init(&pong_subscription, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/pong", &pong_subscription_ops);
145+
146+
// Create a best effort ping subscriber
147+
rcl_subscription_options_t ping_subscription_ops = rcl_subscription_get_default_options();
148+
ping_subscription_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
149+
rcl_subscription_t ping_subscription = rcl_get_zero_initialized_subscription();
150+
rcl_subscription_init(&ping_subscription, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/ping", &ping_subscription_ops);
151+
152+
// Create guard condition
153+
rcl_guard_condition_t guard_condition = rcl_get_zero_initialized_guard_condition();
154+
rcl_guard_condition_options_t guard_condition_options = rcl_guard_condition_get_default_options();
155+
rcl_guard_condition_init(&guard_condition, &context, guard_condition_options);
156+
157+
pthread_t guard_condition_thread;
158+
pthread_create(&guard_condition_thread, NULL, trigger_guard_condition, &guard_condition);
159+
160+
// Create a wait set
161+
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
162+
rcl_wait_set_init(&wait_set, 2, 1, 0, 0, 0, 0, &context, rcl_get_default_allocator());
163+
164+
// Create and allocate the pingpong publication message
165+
std_msgs__msg__Header msg;
166+
char msg_buffer[STRING_BUFFER_LEN];
167+
msg.frame_id.data = msg_buffer;
168+
msg.frame_id.capacity = STRING_BUFFER_LEN;
169+
170+
// Create and allocate the pingpong subscription message
171+
std_msgs__msg__Header rcv_msg;
172+
char rcv_buffer[STRING_BUFFER_LEN];
173+
rcv_msg.frame_id.data = rcv_buffer;
174+
rcv_msg.frame_id.capacity = STRING_BUFFER_LEN;
175+
176+
// Set device id and sequence number;
177+
int device_id = rand();
178+
int seq_no;
179+
180+
int pong_count = 0;
181+
struct timespec ts;
182+
rcl_ret_t rc;
183+
184+
do {
185+
// Clear and set the waitset
186+
rcl_wait_set_clear(&wait_set);
187+
188+
size_t index_pong_subscription;
189+
rcl_wait_set_add_subscription(&wait_set, &pong_subscription, &index_pong_subscription);
190+
191+
size_t index_ping_subscription;
192+
rcl_wait_set_add_subscription(&wait_set, &ping_subscription, &index_ping_subscription);
193+
194+
size_t index_guardcondition;
195+
rcl_wait_set_add_guard_condition(&wait_set, &guard_condition, &index_guardcondition);
196+
197+
// Run session for 100 ms
198+
rcl_wait(&wait_set, RCL_MS_TO_NS(100));
199+
200+
// Check if some pong message is received
201+
if (wait_set.subscriptions[index_pong_subscription]) {
202+
rc = rcl_take(wait_set.subscriptions[index_pong_subscription], &rcv_msg, NULL, NULL);
203+
if(rc == RCL_RET_OK && strcmp(msg.frame_id.data,rcv_msg.frame_id.data) == 0) {
204+
pong_count++;
205+
printf("Pong for seq %s (%d)\n", rcv_msg.frame_id.data, pong_count);
206+
}
207+
}
208+
209+
// Check if some ping message is received and pong it
210+
if (wait_set.subscriptions[index_ping_subscription]) {
211+
rc = rcl_take(wait_set.subscriptions[index_ping_subscription], &rcv_msg, NULL, NULL);
212+
213+
// Dont pong my own pings
214+
if(rc == RCL_RET_OK && strcmp(msg.frame_id.data,rcv_msg.frame_id.data) != 0){
215+
printf("Ping received with seq %s. Answering.\n", rcv_msg.frame_id.data);
216+
rcl_publish(&pong_publisher, (const void*)&rcv_msg, NULL);
217+
}
218+
}
219+
220+
// Check if it is time to send a ping
221+
if (wait_set.guard_conditions[index_guardcondition]) {
222+
// Generate a new random sequence number
223+
seq_no = rand();
224+
sprintf(msg.frame_id.data, "%d_%d", seq_no, device_id);
225+
msg.frame_id.size = strlen(msg.frame_id.data);
226+
227+
// Fill the message timestamp
228+
clock_gettime(CLOCK_REALTIME, &ts);
229+
msg.stamp.sec = ts.tv_sec;
230+
msg.stamp.nanosec = ts.tv_nsec;
231+
232+
// Reset the pong count and publish the ping message
233+
pong_count = 0;
234+
rcl_publish(&ping_publisher, (const void*)&msg, NULL);
235+
printf("Ping send seq %s\n", msg.frame_id.data);
236+
}
237+
238+
usleep(10000);
239+
} while (true);
240+
}
241+
```
242+
243+
Don't forget to register your app in `src/uros/micro-ROS-demos/rcl/CMakeLists.txt` by adding the following line:
244+
245+
```
246+
export_executable(my_brand_new_app)
247+
```
248+
249+
## Running the micro-ROS app
250+
251+
The micro-ROS app is ready to connect to a micro-ROS-Agent and start talking with the rest of the ROS 2 world.
252+
253+
First of all, create a micro-ROS agent:
254+
255+
```bash
256+
# Download micro-ROS-Agent packages
257+
ros2 run micro_ros_setup create_agent_ws.sh
258+
```
259+
260+
When the all client and agent packages are ready, just build them all:
261+
262+
```bash
263+
# Build step
264+
ros2 run micro_ros_setup build_firmware.sh
265+
source install/local_setup.bash
266+
```
267+
268+
Then run the agent:
269+
270+
```bash
271+
# Run a micro-ROS agent
272+
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888
273+
```
274+
275+
Then run the app in another command line (remember sourcing ROS 2 and micro-ROS installation):
276+
277+
```bash
278+
source /opt/ros/$ROS_DISTRO/setup.bash
279+
source install/local_setup.bash
280+
281+
# Run a micro-ROS agent
282+
ros2 run micro_ros_demos_rcl my_brand_new_app
283+
```
284+
285+
And finally, let's check that everything is working in another command line. We are going to listen to ping topic to check whether the Ping Pong node is publishing its own pings:
286+
287+
```bash
288+
source /opt/ros/$ROS_DISTRO/setup.bash
289+
290+
# Subscribe to micro-ROS ping topic
291+
ros2 topic echo /microROS/ping
292+
```
293+
294+
You should see the topic messages published by the Ping Pong node every 5 seconds:
295+
296+
```
297+
user@user:~$ ros2 topic echo /microROS/ping
298+
stamp:
299+
sec: 20
300+
nanosec: 867000000
301+
frame_id: '1344887256_1085377743'
302+
---
303+
stamp:
304+
sec: 25
305+
nanosec: 942000000
306+
frame_id: '730417256_1085377743'
307+
---
308+
```
309+
310+
On another command line, let's subscribe to the pong topic
311+
312+
```bash
313+
source /opt/ros/$ROS_DISTRO/setup.bash
314+
315+
# Subscribe to micro-ROS pong topic
316+
ros2 topic echo /microROS/pong
317+
```
318+
319+
At this point, we know that our app is publishing pings. Let's check if it also answers to someone else pings in a new command line:
320+
321+
```bash
322+
source /opt/ros/$ROS_DISTRO/setup.bash
323+
324+
# Send a fake ping
325+
ros2 topic pub --once /microROS/ping std_msgs/msg/Header '{frame_id: "fake_ping"}'
326+
```
327+
328+
Now, we should see on the ping subscriber our fake ping along with the board pings:
329+
330+
```
331+
user@user:~$ ros2 topic echo /microROS/ping
332+
stamp:
333+
sec: 0
334+
nanosec: 0
335+
frame_id: fake_ping
336+
---
337+
stamp:
338+
sec: 305
339+
nanosec: 973000000
340+
frame_id: '451230256_1085377743'
341+
---
342+
stamp:
343+
sec: 310
344+
nanosec: 957000000
345+
frame_id: '2084670932_1085377743'
346+
---
347+
```
348+
349+
And in the pong subscriber, we should see the board's answer to our fake ping:
350+
351+
```
352+
user@user:~$ ros2 topic echo /microROS/pong
353+
stamp:
354+
sec: 0
355+
nanosec: 0
356+
frame_id: fake_ping
357+
---
358+
```
359+
360+
361+
## Multiple Ping Pong nodes
362+
363+
One of the advantages of having an Linux micro-ROS app is that you don't need to buy a bunch of hardware in order to test some multi-node micro-ROS apps. So, with the same micro-ROS agent of the last section, let's open four different command lines and run the following on each:
364+
365+
```bash
366+
cd microros_ws
367+
368+
source /opt/ros/$ROS_DISTRO/setup.bash
369+
source install/local_setup.bash
370+
371+
ros2 run micro_ros_demos_rcl my_brand_new_app
372+
```
373+
374+
As soon as all micro-ROS nodes are up and connected to the micro-ROS agent you will see them interacting:
375+
376+
```
377+
user@user$ ros2 run micro_ros_demos_rcl my_brand_new_app
378+
UDP mode => ip: 127.0.0.1 - port: 8888
379+
Ping send seq 1711620172_1742614911 <---- This micro-ROS node sends a ping with ping ID "1711620172" and node ID "1742614911"
380+
Pong for seq 1711620172_1742614911 (1) <---- The first mate pongs my ping
381+
Pong for seq 1711620172_1742614911 (2) <---- The second mate pongs my ping
382+
Pong for seq 1711620172_1742614911 (3) <---- The third mate pongs my ping
383+
Ping received with seq 1845948271_546591567. Answering. <---- A ping is received from a mate identified as "546591567", let's pong it.
384+
Ping received with seq 232977719_1681483056. Answering. <---- A ping is received from a mate identified as "1681483056", let's pong it.
385+
Ping received with seq 1134264528_1107823050. Answering. <---- A ping is received from a mate identified as "1107823050", let's pong it.
386+
Ping send seq 324239260_1742614911
387+
Pong for seq 324239260_1742614911 (1)
388+
Pong for seq 324239260_1742614911 (2)
389+
Pong for seq 324239260_1742614911 (3)
390+
Ping received with seq 1435780593_546591567. Answering.
391+
Ping received with seq 2034268578_1681483056. Answering.
392+
```

0 commit comments

Comments
 (0)