
micro-ROSを使うとマイコン上にROS2のノードを作れるということなのでやってみた。
ESP32のAPPでトピックを直接扱えるのはありがたい。
micro-ROS-AgentをPCやRasb\pberry Piなどのどこかで起動していれば、通信を意識しなくてよくなる。
しかもmicro-ROS-AgentはDocker一発でインストールと起動ができるので楽に使える。
Arduino IDEへmicro_ros_arduinoライブラリをインストールするやり方はTakumi Asadaの記事を参考にさせて頂いた。
環境
現在の環境は下図の通り。
ESP32のプログラムは、ラズパイ5上のArduino IDE 1.8.19で作成し、アップロードする。
現在、ESP32との接続はすべてWi-Fiとし、micro-ROS-AgentはPCのubuntuに固定。
PC, ラズパイ5, WSL2のDOMAIN_IDを1に設定していて、ESP32のAPPとはいずれからも通信可能。
micro-ROS-Agentは、PCでもラズパイ5でも「使い方」のDockerコマンドでインストールが可能なのでこれで良しとする。但しWSL2では使えなかった。

動作確認
例題の「micro_ros_publisher_wifi.ino」を少し修正して動作確認用プログラムとする。
プログラム内でカウントアップした整数がパブリッシュされる(画像参照)。
使用中の環境は、DOMAIN_IDを1に設定しているので同様にDOMAIN_IDをに設定した。
micro_ROS_Agent を起動したPCのIPアドレスを環境に合わせて修正すること。
micro_ros_publisher_wifi_DOMAIN_ID
/* micro_ros_publisher_wifi_DOMAIN_ID 2025.1.12
例題プログラム micro_ros_publisher_wifi にDOMAIN_ID 1 を追加した。
*/
#include <micro_ros_arduino.h>
#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <std_msgs/msg/int32.h>
#if !defined(ESP32) && !defined(TARGET_PORTENTA_H7_M7) && !defined(ARDUINO_NANO_RP2040_CONNECT) && !defined(ARDUINO_WIO_TERMINAL)
#error This example is only avaible for Arduino Portenta, Arduino Nano RP2040 Connect, ESP32 Dev module and Wio Terminal
#endif
rcl_publisher_t publisher;
std_msgs__msg__Int32 msg;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_node_t node;
rcl_init_options_t init_options; //[追加]
rmw_init_options_t* rmw_options; //[追加]
#define DOMAIN_ID 1 //[追加]
#define LED_PIN 2 // 13 -> 2
#define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){error_loop();}}
#define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){}}
void error_loop(){
while(1){
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
delay(100);
}
}
void timer_callback(rcl_timer_t * timer, int64_t last_call_time)
{
RCLC_UNUSED(last_call_time);
if (timer != NULL) {
RCSOFTCHECK(rcl_publish(&publisher, &msg, NULL));
msg.data++;
}
}
void setup() {
set_microros_wifi_transports("aterm-2b4139-a", "3e00cfa4ba409", "192.168.0.63", 8888);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
delay(2000);
allocator = rcl_get_default_allocator();
// create init_options ---------------------- オリジナル
//RCCHECK(rclc_support_init(&support, 0, NULL, &allocator)); --------------------- オリジナル
// Init options to use domain id ---- DOMEIN IDを設定する
init_options = rcl_get_zero_initialized_init_options();
RCCHECK(rcl_init_options_init(&init_options, allocator));
rmw_options = rcl_init_options_get_rmw_init_options(&init_options);
rcl_init_options_set_domain_id(&init_options, (size_t)DOMAIN_ID);
RCCHECK(rclc_support_init_with_options(&support, 0, NULL, &init_options, &allocator));
// create node
RCCHECK(rclc_node_init_default(&node, "micro_ros_arduino_wifi_node", "", &support));
// create publisher
RCCHECK(rclc_publisher_init_best_effort(
&publisher,
&node,
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
"topic_name"));
msg.data = 0;
}
void loop() {
RCSOFTCHECK(rcl_publish(&publisher, &msg, NULL));
msg.data++;
}
使い方
[端末1] --- PC上でmicro-ROS-Agentを起動する。
sudo docker run -it --rm -v /dev:/dev --privileged --net=host microros/micro-ros-agent:jazzy udp4 --port 8888
[端末2] --- ESP32からのトピックを表示する。 --- PC, Raspi5, WSL2
ros2 topic echo /topic_name

エコープログラム
String型のデータをサブスクライブしてそのままパブリッシュするプログラム。ESP32でも状態が分かるようにサブスクライブしたデータをOLED(SSD1306)に表示する。
micro_ROS_pub_sub_OLED
#include <dummy.h>
/* micro_ROS_pub_sub_OLED.ino 2024.7.31
【概要】受信したメッセージをエコーバックする。
String型のトピックをsubscribeして、メッセージに受信回数を付加してpublishする。
受信したメッセージをOLEDに表示する。
subscribe: /recv (String)
publish: /echo (String)
*/
#include <Arduino.h>
#include <micro_ros_arduino.h>
#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <std_msgs/msg/string.h>
#include <Wire.h> // SSD1306 --- OLED用
#include "SSD1306.h" // SSD1306用ライブラリを読み込み
SSD1306 display(0x3c, 21, 22); //SSD1306インスタンスの作成(I2Cアドレス,SDA,SCL)
rcl_node_t node;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_init_options_t init_options; //[追加]
rmw_init_options_t* rmw_options; //[追加]
// publisher
rcl_publisher_t publisher;
std_msgs__msg__String send_string_msg;
rclc_executor_t executor_pub;
rcl_timer_t timer;
// subscriber
rcl_subscription_t subscriber;
std_msgs__msg__String recv_string_msg;
#define STR_SIZE (100) //最大の受信文字数
rclc_executor_t executor_sub;
#define DOMAIN_ID 1 //[追加]
#define LED_PIN 2
#define RCCHECK(fn) \
{ \
rcl_ret_t temp_rc = fn; \
if ((temp_rc != RCL_RET_OK)) \
{ \
error_loop(); \
} \
}
#define RCSOFTCHECK(fn) \
{ \
rcl_ret_t temp_rc = fn; \
if ((temp_rc != RCL_RET_OK)) \
{ \
} \
}
int sub_cnt = 0; // subscription回数
/**
* @brief loop to indicate error with blinking LED
*
*/
void error_loop()
{
while (1)
{
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
//[追加] OLEDにエラーを表示する。
String err_msg = "";
if (LED_PIN == 0) {
err_msg = " Error";
} else {
err_msg = " ";
}
display.drawString(0, 17, err_msg);
display.display(); // 画面を表示する
}
delay(200);
}
int cnt = 0;
void timer_callback(rcl_timer_t *timer, int64_t last_call_time)
{
RCLC_UNUSED(last_call_time);
if (timer != NULL)
{
//RCSOFTCHECK(rcl_publish(&publisher, &send_string_msg, NULL));
//msg_heartbeat.data++;
cnt++;
}
}
/**
* @brief subscription callback executed at receiving a message
*
* @param msgin
*/
void subscription_callback(const void *msgin)
{
const std_msgs__msg__String *recv_string_msg = (const std_msgs__msg__String *)msgin;
// (condition) ? (true exec):(false exec)
//digitalWrite(LED_PIN, (recv_string_msg->data == 0) ? LOW : HIGH);
sub_cnt++; // subscription回数カウントアップ
// 送信メッセージ
String s = String(recv_string_msg->data.data) + String(sub_cnt);
//----- OLED表示
display.init(); // 表示クリア
// 1行目のテキストの表示 --- subscription回数
display.drawString(0, 0, "msg_cnt: "+String(sub_cnt));
// 2行目のテキストの表示 --- メッセージ + subscription回数
String line2 = String(recv_string_msg->data.data);
display.drawString(0, 17, line2);
display.display(); // 画面を表示する。
// 送信メッセージをpublish形式に変換する。
char strBuf[120];
s.toCharArray(strBuf, 120);
send_string_msg.data.size = s.length();
send_string_msg.data.data = strBuf;
RCSOFTCHECK(rcl_publish(&publisher, &send_string_msg, NULL));
}
void setup()
{
//[追加]----- 初期画面表示
display.init(); //ディスプレイを初期化
display.setFont(ArialMT_Plain_16); //フォントを設定
display.drawString(0, 0, "*Program name*"); //(0,0)の位置に表示
display.drawString(0, 17, "micro_ROS_pub_sub_OLED.ino");
display.drawString(0, 34, "sub_OLED.ino");
display.display(); //指定された情報を描画
//set_microros_transports();
set_microros_wifi_transports("aterm-2b4139-a", "3e00cfa4ba409", "192.168.0.63", 8888);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
delay(3000);
allocator = rcl_get_default_allocator();
// create init_options ---------------------- オリジナル
//RCCHECK(rclc_support_init(&support, 0, NULL, &allocator)); --------------------- オリジナル
// Init options to use domain id ---- DOMEIN IDを設定する
init_options = rcl_get_zero_initialized_init_options();
RCCHECK(rcl_init_options_init(&init_options, allocator));
rmw_options = rcl_init_options_get_rmw_init_options(&init_options);
rcl_init_options_set_domain_id(&init_options, (size_t)DOMAIN_ID);
RCCHECK(rclc_support_init_with_options(&support, 0, NULL, &init_options, &allocator));
// create node
RCCHECK(rclc_node_init_default(&node, "micro_ros_xiao_node", "", &support));
// create subscriber
// const char topic_name_led[] = "recv";
RCCHECK(rclc_subscription_init_default(
&subscriber,
&node,
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String),
"recv"));
// create publisher
// const char topic_name_heatbeat[] = "echo";
RCCHECK(rclc_publisher_init_default(
&publisher,
&node,
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, String),
"echo"));
// create timer, called every 1000 ms to publish heartbeat
const unsigned int timer_timeout = 500;
RCCHECK(rclc_timer_init_default(
&timer,
&support,
RCL_MS_TO_NS(timer_timeout),
timer_callback));
// create executor
RCCHECK(rclc_executor_init(&executor_pub, &support.context, 1, &allocator));
RCCHECK(rclc_executor_add_timer(&executor_pub, &timer));
RCCHECK(rclc_executor_init(&executor_sub, &support.context, 1, &allocator));
RCCHECK(rclc_executor_add_subscription(&executor_sub, &subscriber, &recv_string_msg, &subscription_callback, ON_NEW_DATA));
// char型変数を入れる配列を確保する
recv_string_msg.data.data = (char * )malloc(STR_SIZE * sizeof(char));
recv_string_msg.data.size = 0;
recv_string_msg.data.capacity = STR_SIZE;
}
void loop()
{
delay(10);
RCCHECK(rclc_executor_spin_some(&executor_pub, RCL_MS_TO_NS(100)));
RCCHECK(rclc_executor_spin_some(&executor_sub, RCL_MS_TO_NS(100)));
}
使い方
[端末1] --- PC上でmicro-ROS-Agentを起動する。
sudo docker run -it --rm -v /dev:/dev --privileged --net=host microros/micro-ros-agent:jazzy udp4 --port 8888
[端末2] --- ESP32のノードにトピックを送る。 --- PC, Raspi5, WSL2
ros2 topic pub --once /recv std_msgs/msg/String "{data: 'hello world'}"
[端末3] --- ESP32からのトピックを表示する。 --- PC, Raspi5, WSL2
ros2 topic echo /echo
課題
●ESP32C3用のmicro-ROS for Arduinoがまだない
esp32S3 または esp32C3 のアップデート計画はありますか? #1820
●WSL2をインストールしたPCでESP32C3はCOMボートを認識するが、ESP32は認識しない
micro-ROSの開発環境はRaspi5になる。
●Raspi5とWSL2のROS2でトピックが通らない。——–[解決]
Raspi5をファイアウォールの「リモートIPアドレス」に追加が抜けていた。参照(WSL2にミラーモードを設定する)