Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 16 additions & 218 deletions _docs/tutorials/advanced/zephyr_emulator/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ title: Zephyr Emulator
permalink: /docs/tutorials/advanced/zephyr_emulator/
---

This tutorial aims to create a new micro-ROS application on with **[Zephyr RTOS](https://www.zephyrproject.org/)** emulator (also known as [Native POSIX](https://docs.zephyrproject.org/latest/boards/posix/native_posix/doc/index.html)).
This tutorial aims at creating a new micro-ROS application on with **[Zephyr RTOS](https://www.zephyrproject.org/)** emulator (also known as [Native POSIX](https://docs.zephyrproject.org/latest/boards/posix/native_posix/doc/index.html)).

To follow this tutorial, it is assumed that the user is already familiar with the **[First micro-ROS Application on an RTOS](https://micro-ros.github.io/docs/tutorials/core/first_application_rtos/)** tutorial. The target app in this tutorial is the same ping pong app.
Another requirement is that the user has a basic knowledge of micro-ROS and ROS 2.

<div>
<img width="300" style="padding-right: 25px;" src="imgs/4.jpg">
Expand All @@ -13,244 +16,39 @@ This tutorial aims to create a new micro-ROS application on with **[Zephyr RTOS]

This tutorial requires no hardware beyond a Linux host computer.

## Adding a new micro-ROS app

First of all, make sure that you have a **ROS 2** installation.

***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)*

On the **ROS 2** installation open a command line and follow these steps:

```bash
# Source the ROS 2 installation
source /opt/ros/$ROS_DISTRO/setup.bash

# Create a workspace and download the micro-ROS tools
mkdir microros_ws
cd microros_ws
git clone -b $ROS_DISTRO https://github.com/micro-ROS/micro-ros-build.git src/micro-ros-build

# Update dependencies using rosdep
sudo apt update && sudo apt install python3-colcon-metadata
rosdep update
rosdep install --from-path src --ignore-src -y

# Build micro-ROS tools and source them
colcon build
source install/local_setup.bash
```
## Building a Zephyr emulator application

Let's install the last version of CMake:

```bash
sudo apt install wget
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add -
sudo apt install software-properties-common
sudo apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main'
sudo apt update
sudo apt install cmake
```

Now, let's create a firmware workspace that targets all the required code and tools for Zephyr emulator:
Once the micro-ROS build system is ready, let's create a new Zephyr firmware for the host platform:

```bash
# Create firmware step
ros2 run micro_ros_setup create_firmware_ws.sh zephyr host
```

Now you have all the required tools to compile micro-ROS and Zephyr. At this point, you must know that the micro-ROS build system is a four-step workflow:

<!-- TODO (pablogs9): Remove and link to build-system tutorial when done -->
1. **Create**: retrieves all the required packages for a specific RTOS and hardware platform.
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).
3. **Build**: generates a binary file ready for being loaded in the hardware.
4. **Flash**: load the micro-ROS software in the hardware.

micro-ROS apps for Zephyr emulator are located at `firmware/zephyr_apps/apps`. In order to create a new application, create a new folder containing two files: the app code (inside a `src` folder) and the RMW configuration. You will also need some other Zephyr related files: a `CMakeLists.txt` to define the building process and a `prj.conf` where Zephyr is configured. You have these two files [here](https://github.com/micro-ROS/zephyr_apps/tree/dashing/apps/host_ping_pong), for now, it is ok to copy them.
micro-ROS apps for Zephyr emulator are located at `firmware/zephyr_apps/apps`. In order to create a new application, create a new folder containing two files: the app code (inside a `src` folder) and the RMW configuration. You will also need some other Zephyr related files: a `CMakeLists.txt` to define the building process and a `prj.conf` where Zephyr is configured. There is a sample proyect [here](https://github.com/micro-ROS/zephyr_apps/tree/dashing/apps/host_ping_pong), for now, it is ok to copy them.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
micro-ROS apps for Zephyr emulator are located at `firmware/zephyr_apps/apps`. In order to create a new application, create a new folder containing two files: the app code (inside a `src` folder) and the RMW configuration. You will also need some other Zephyr related files: a `CMakeLists.txt` to define the building process and a `prj.conf` where Zephyr is configured. There is a sample proyect [here](https://github.com/micro-ROS/zephyr_apps/tree/dashing/apps/host_ping_pong), for now, it is ok to copy them.
micro-ROS apps for the Zephyr emulator are located at `firmware/zephyr_apps/apps`. In order to create a new application, create a new folder containing two files: the app code (inside a `src` folder) and the RMW configuration. You will also need some other Zephyr related files: a `CMakeLists.txt` to define the building process and a `prj.conf` where Zephyr is configured. You can find a sample project [here](https://github.com/micro-ROS/zephyr_apps/tree/dashing/apps/host_ping_pong). For this tutorial, it is ok to copy them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I liked better when all this was shown rather than explained. I mean, just the commands, and then references to where to find the files content to copy.
I mean, I would use the same text as the old tutorial, with the pushd and popd commands and everything, and only change the way to refer the user to the files content.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Can't you make those links cliackable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


```bash
# Creating a new app
pushd firmware/zephyr_apps/apps
mkdir my_brand_new_app
cd my_brand_new_app
mkdir host_ping_pong
cd host_ping_pong
mkdir src
touch src/app.c app-colcon.meta

# Copying CMakeLists.txt and prj.conf
wget https://raw.githubusercontent.com/micro-ROS/zephyr_apps/dashing/apps/host_ping_pong/CMakeLists.txt
wget https://raw.githubusercontent.com/micro-ROS/zephyr_apps/dashing/apps/host_ping_pong/prj.conf
touch src/app.c
touch app-colcon.meta
touch CMakeLists.txt
touch prj.conf

popd
```

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:

![pingpong](http://www.plantuml.com/plantuml/png/ZOwnIWGn48RxFCNFzSkoUG2vqce5jHEHi1dtWZkPa6GByNntavZY10yknMJu-ORlFwPiOjvvK-d3-M2YOR1uMKvHc93ZJafvoMML07d7h1NAE-DPWblg_na8vnwEx9OeZmzFOt1-BK7AzetJciPxCfRYVw1S0SbRLBEg1IpXPIvpUWLCmZpXIm6BS3addt7uQpu0ZQlxT1MK2r0g-7sfqbsbRrVfMrMwgbev3CDTlsqJGtJhATUmSMrMg5TKwaZUxfcttuMt7m00)

To start creating this app, lets configure the RMW with the required static memory. You can read more about RMW and Micro XRCE-DDS Configuration [here](/docs/tutorials/core/microxrcedds_rmw_configuration/). The `app-colcon.meta` should look like:

```
{
"names": {
"rmw_microxrcedds": {
"cmake-args": [
"-DRMW_UXRCE_MAX_NODES=1",
"-DRMW_UXRCE_MAX_PUBLISHERS=2",
"-DRMW_UXRCE_MAX_SUBSCRIPTIONS=2",
"-DRMW_UXRCE_MAX_SERVICES=0",
"-DRMW_UXRCE_MAX_CLIENTS=0",
"-DRMW_UXRCE_MAX_HISTORY=4",
]
}
}
}
```

Meanwhile `src/app.c` should look like the following code:

```c
#include <rcl/rcl.h>
#include <rcl_action/rcl_action.h>
#include <rcl/error_handling.h>
#include "rosidl_generator_c/string_functions.h"
#include <std_msgs/msg/header.h>

#include <rmw_uros/options.h>

#include <stdio.h>
#include <unistd.h>
#include <time.h>

#include <zephyr.h>

#define STRING_BUFFER_LEN 100

// App main function
void main(void)
{
//Init RCL options
rcl_init_options_t options = rcl_get_zero_initialized_init_options();
rcl_init_options_init(&options, rcl_get_default_allocator());

// Init RCL context
rcl_context_t context = rcl_get_zero_initialized_context();
rcl_init(0, NULL, &options, &context);

// Create a node
rcl_node_options_t node_ops = rcl_node_get_default_options();
rcl_node_t node = rcl_get_zero_initialized_node();
rcl_node_init(&node, "pingpong_node", "", &context, &node_ops);

// Create a reliable ping publisher
rcl_publisher_options_t ping_publisher_ops = rcl_publisher_get_default_options();
rcl_publisher_t ping_publisher = rcl_get_zero_initialized_publisher();
rcl_publisher_init(&ping_publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/ping", &ping_publisher_ops);

// Create a best effort pong publisher
rcl_publisher_options_t pong_publisher_ops = rcl_publisher_get_default_options();
pong_publisher_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
rcl_publisher_t pong_publisher = rcl_get_zero_initialized_publisher();
rcl_publisher_init(&pong_publisher, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/pong", &pong_publisher_ops);

// Create a best effort pong subscriber
rcl_subscription_options_t pong_subscription_ops = rcl_subscription_get_default_options();
pong_subscription_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
rcl_subscription_t pong_subscription = rcl_get_zero_initialized_subscription();
rcl_subscription_init(&pong_subscription, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/pong", &pong_subscription_ops);

// Create a best effort ping subscriber
rcl_subscription_options_t ping_subscription_ops = rcl_subscription_get_default_options();
ping_subscription_ops.qos.reliability = RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT;
rcl_subscription_t ping_subscription = rcl_get_zero_initialized_subscription();
rcl_subscription_init(&ping_subscription, &node, ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Header), "/microROS/ping", &ping_subscription_ops);

// Create a wait set
rcl_wait_set_t wait_set = rcl_get_zero_initialized_wait_set();
rcl_wait_set_init(&wait_set, 2, 0, 0, 0, 0, 0, &context, rcl_get_default_allocator());

// Create and allocate the pingpong publication message
std_msgs__msg__Header msg;
char msg_buffer[STRING_BUFFER_LEN];
msg.frame_id.data = msg_buffer;
msg.frame_id.capacity = STRING_BUFFER_LEN;

// Create and allocate the pingpong subscription message
std_msgs__msg__Header rcv_msg;
char rcv_buffer[STRING_BUFFER_LEN];
rcv_msg.frame_id.data = rcv_buffer;
rcv_msg.frame_id.capacity = STRING_BUFFER_LEN;

// Set device id and sequence number;
int device_id = rand();
int seq_no;

int pong_count = 0;
struct timespec ts;
rcl_ret_t rc;

uint32_t iterations = 0;

do {
// Clear and set the waitset
rcl_wait_set_clear(&wait_set);

size_t index_pong_subscription;
rcl_wait_set_add_subscription(&wait_set, &pong_subscription, &index_pong_subscription);

size_t index_ping_subscription;
rcl_wait_set_add_subscription(&wait_set, &ping_subscription, &index_ping_subscription);

// Run session for 100 ms
rcl_wait(&wait_set, RCL_MS_TO_NS(100));

// Check if some pong message is received
if (wait_set.subscriptions[index_pong_subscription]) {
rc = rcl_take(wait_set.subscriptions[index_pong_subscription], &rcv_msg, NULL, NULL);
if(rc == RCL_RET_OK && strcmp(msg.frame_id.data,rcv_msg.frame_id.data) == 0) {
pong_count++;
printf("Pong for seq %s (%d)\n", rcv_msg.frame_id.data, pong_count);
}
}

// Check if some ping message is received and pong it
if (wait_set.subscriptions[index_ping_subscription]) {
rc = rcl_take(wait_set.subscriptions[index_ping_subscription], &rcv_msg, NULL, NULL);

// Dont pong my own pings
if(rc == RCL_RET_OK && strcmp(msg.frame_id.data,rcv_msg.frame_id.data) != 0){
printf("Ping received with seq %s. Answering.\n", rcv_msg.frame_id.data);
rcl_publish(&pong_publisher, (const void*)&rcv_msg, NULL);
}
}

// Check if it is time to send a ping
if (iterations++ % 50 == 0) {
// Generate a new random sequence number
seq_no = rand();
sprintf(msg.frame_id.data, "%d_%d", seq_no, device_id);
msg.frame_id.size = strlen(msg.frame_id.data);

// Fill the message timestamp
clock_gettime(CLOCK_REALTIME, &ts);
msg.stamp.sec = ts.tv_sec;
msg.stamp.nanosec = ts.tv_nsec;

// Reset the pong count and publish the ping message
pong_count = 0;
rcl_publish(&ping_publisher, (const void*)&msg, NULL);
printf("Ping send seq %s\n", msg.frame_id.data);
}

usleep(10000);
} while (true);
}
```
The contents of the files can be found here: [app.c](https://github.com/micro-ROS/zephyr_apps/blob/dashing/apps/host_ping_pong/src/main.c), [app-colcon.meta](https://github.com/micro-ROS/zephyr_apps/blob/dashing/apps/host_ping_pong/app-colcon.meta), [CMakeLists.txt](https://github.com/micro-ROS/zephyr_apps/blob/dashing/apps/host_ping_pong/CMakeLists.txt) and [prj.conf](https://github.com/micro-ROS/zephyr_apps/blob/dashing/apps/host_ping_pong/prj.conf).

Once the new folder is created, let's configure our new app with a UDP transport that looks for the agent on the port UDP/8888 at localhost:
Once the app folder is created, let's configure our new app with a UDP transport that looks for the agent on the port UDP/8888 at localhost:

```bash
# Configure step
ros2 run micro_ros_setup configure_firmware.sh my_brand_new_app --transport udp --ip 127.0.0.1 --port 8888
ros2 run micro_ros_setup configure_firmware.sh host_ping_pong --transport udp --ip 127.0.0.1 --port 8888
```

When the configuring step ends, just build the firmware:
Expand Down