TSM - Introducere în ROS – Robot Operating System

Ovidiu Mățan - Fondator @ Today Software Magazine


Robotic Operating System este un framework specializat în dezvoltarea de aplicații de IoT și în general, de robotică. Avantajele față de o soluție inhouse pentru proiectele de IoT este abstractizarea părții de comunicare între procese, ușurința prin care se pot crea servicii, mecanismele publish - subscriber, parametri globali și multe altele. În acest articol vom aborda conceptele de bază și vom exemplifica comunicarea dintre un SBC (single board computer) și o placă de dezvoltare Arduino.

În articolele anterioare legate de Mașinuța IOT, am arătat cum ne putem transforma în realitate ideile din copilărie, respectiv, să avem o mașinuță teleghidată care să ne recunoască și să interacționeze cu noi. În acele proiecte, am folosit direct implementările în Pyhton și în Arduino pentru a realiza partea de control, comunicare și procesare a datelor. Acum vom simplifica și optimiza întreaga aplicație prin folosirea Robot Operating Systems.

Overview

Dezvoltarea ROS a fost conceput în 2007 de doi doctoranzi care lucrau în laboratorul de robotică de la Universitatea Standford: Eric Berger și Keenan Wyrobek. Ei au început colaborarea cu Scott Hassan, fondatorul Willow Garage unde au și lansat prima versiune. ROS a fost realizat ca un proiect open-source care ajută la dezvoltarea proiectelor de robotică prin re-folosirea diferitelor librării și componente.

Chiar dacă numele ne lasă să credem că vorbim despre un sistem de operare, în realitate, ROS este un framework care rulează pe Linux sau Windows iar pentru MacOS există o variantă experimentală. Bineînțeles că ne putem compila sursele pentru distribuțiile de Linux și platformele noastre hardware. În cazul de față am folosit una dintre ultimele plăci SBC de la NVidia: Jetson Nano care ne oferă un procesor ARM, 4GB RAM și un GPU cu 128 de core-uri CUDA. Instalarea ROS nu a fost lipsită de peripeții și ne face plăcere să vă oferim câteva tips & tricks.

Arhitectura ROS

Reprezintă o rețea distribuită peer to peer unde avem un nod principal: ROS Master care coordonează comunicarea folosind un API bazat pe XMLRPC astfel:

Ref: https://www.researchgate.net/figure/ROS-graph-concepts-a-Core-computation-graph-level-b-messages-publish-and-subscribe_fig2_323709086

Limbajele de programare suportate oficial sunt:

Instalare

Instalarea non-standard pe diferite platforme IoT se poate efectua prin realizarea unui build folosind sursele git. Mai multe detalii pentru instalarea ultimei versiuni ROS, Morenia găsiți aici.

Un aspect important este Catkin, environmentul în care se mențin sursele și pachetele instalate

Turtelism

Turtelsim este un tool realizat pentru învățarea ROS. În continuare, vom vedea folosirea nodurilor ROS în contextul controlării unei broscuțe țestoasă.

A. Pornim nodul master

$ roscore

Vom folosi comanda rosrun pentru a porni un nod:

rosrun  

B. Lansăm nodul turtelism

$ rosrun turtlesim turtlesim_node

D. Controlul broscuței folosind tastele se realizează prin pornirea unui nou nod turtelism care va publica tastele apăsate.

$ rosrun turtlesim turtle_teleop_key

E. Desenarea unui pătrat se realizează prin lansarea unui nod nou

$ rosrun turtlesim draw_square

Hello World

Am văzut cum se lansează nodurile în ROS, vom continua cu o simplă aplicație Echo ce va folosi un Topic pentru trimiterea mesajelor între două noduri folosind mecanismul Publish / Subscriber.

#!/usr/bin/env python
import rospy
from std_msgs.msg import String
def talker():
pub = rospy.Publisher('chatter', String, 
queue_size=10)

rospy.init_node('talker', anonymous=True)
rate = rospy.Rate(10) # 10hz

while not rospy.is_shutdown():
    hello_str = "hello world %s" % rospy.get_time()
    rospy.loginfo(hello_str)
    pub.publish(hello_str)
    rate.sleep()

talker()

Prima linie este destul de importantă și vă poate cauza destule nedumeriri, dacă nu o includeți. Declară fișierul curent unul scris în Python, fără această limie se va considera un fișier bash linux.

Remarcăm definirea nodului Talker cu parametru de anonymous=True, acesta permite execuția nodurilor cu același nume prin adăugarea unui număr aleatoriu în numele acestuia.

Publicarea unui mesaj definește numele topicului, în cazul nostru 'chatter', urmat de tipul mesajului și de dimensiunea cozii de mesaje. Dacă se dorește stocarea doar a valorii curente se poate folosi valoarea 1. Odată definit mesajul se poate aplica metoda publish() care îl va trimite la nodul master, și ulterior la nodurile care s-au înregistrat în topicul nostru.

Un ultim amănunt, metoda rospy.Rate(10) va defini un delay de 1/10 sec în momentul apelării rate.sleep()

import rospy
from std_msgs.msg import String

def callback(data):
rospy.loginfo("I heard %s",data.data)

def listener():
    rospy.init_node('node_name')
    rospy.Subscriber("chatter", String, callback)
    # spin() simply keeps python from exiting until 
    # this node is stopped

    rospy.spin()

Ascultarea mesajelor publicate pe topicul chatter implică definirea unui callback. Metoda rospy.spin() va delega terminarea aplicației nodului ROS, eficientizând primirea / procesarea mesajelor pe durata execuției nodului curent.

Controlul unui nod Arduino

Dacă până acum am văzut bazele unei aplicații ROS, este momentul să vedem ceva concret. Vom conecta la placa noastră Jetson Nano un Arduino folosind un cablu USB. Am văzut într-un articol precedent că nu este atât de simplu să realizăm această comunicare care a implicat inclusiv definirea unui protocol pentru comenzile trimise.

Începem prin instalarea IDE-ului Arduino folosind pașii de aici. Nu uitați să dați un restart la final și de asemenea, să instalați pyserial: Pip install pyserial

Aplicația va avea un nod care va realiza comunicarea cu Arduino și altul care va trimite mesaje către acesta. Codul Arduino este următorul:

#include <ros.h>
#include <std_msgs/Empty.h>
#include <std_msgs/Int32.h>

ros::NodeHandle nh;

void blinkLed(int count){
  for (int i=0;i<count;i++){
   digitalWrite(LED_BUILTIN, HIGH-digitalRead(LED_BUILTIN));   
   // blink the led
   
   delay(100);
  }
}

void messageCb( const std_msgs::Int32& value){
  blinkLed(value.data);
}

void goLeft( const std_msgs::Empty& toggle_msg){
  digitalWrite(LED_BUILTIN, HIGH);   // led is On
}

void goRight( const std_msgs::Empty& toggle_msg){
  digitalWrite(LED_BUILTIN, 0);   // led is Off
}

ros::Subscriber<std_msgs::Int32> sub("toggle_led", &messageCb );

ros::Subscriber<std_msgs::Empty> left("go_left", &goLeft );

ros::Subscriber<std_msgs::Empty> right("go_right", 
   &goRight );

void setup()
{ 
  pinMode(LED_BUILTIN, OUTPUT);
  nh.initNode();
  nh.subscribe(sub);
  nh.subscribe(left);
  nh.subscribe(right);

}

void loop()
{  
  nh.spinOnce();
  delay(1);
}

Pentru aplicația noastră vom avea trei topicuri de mesaje la care ne vom înregistra și vom delega execuția către metode specifice:

Pornim nodul pentru comunicarea cu portul serial:

rosrun rosserial_python serial_node.py /dev/ttyUSB0

Pentru lansarea comenzilor deschideți un nou terminal de unde putem lansa:

rostopic pub toggle_led std_msgs/Int32 20 -once

rostopic pub go_left std_msgs/Empty -once

rostopic pub go_left std_msgs/Empty -once

Bineînțeles că putem publica mesaje pe topicurile Arduino și dintr-un alt nod. Vom scrie un client în Python care va publica în topicul Talker mesajul toggle_led cu parametrul 10. Pentru aceasta deschidem folderul /catkin_ws/src/ unde vom crea un modul nou:

catkin_create arduino_msgs rospy

Adăugăm un folder /script în structura nou creată: /catkin_ws/src/arduino_msgs. Definim un nou fișier talkerArduino.py

#!/usr/bin/env python
import rospy
import time
from std_msgs.msg import Int32
def talker():
    rospy.init_node('talker', anonymous=True)
    pub = rospy.Publisher('toggle_led', Int32, queue_size=1)

    time.sleep(1)
    print "sending message"
    pub.publish(10)
   
talker()

Se creează un nou nod: talker din care vom publica Topicul: toggle_led care are un parametru de tipul Int32. Se remarcă sleepul de o secundă necesar pentru o funcționare corectă. Se pare că fără prezența acestuia mesajul nu ajunge la nodul de Arduino.

Lansăm aplicația:

rosrun arduino_msgs talkerArduino.py

iar pe boardul Arduino vom vedea ledurile pâlpâind, semn că mesajul nostru a ajuns.

Concluzie

Sperăm că v-am deschis apetitul pentru ROS și filozofia acestuia de a simplifica lucrurile. Deși necesită un pic de exercițiu pentru instalare, în special pe sistemele ARM, avantajele refolosirii componentelor existente precum și integrările cu diferite sisteme reprezintă un pas înainte în dezvoltarea oricărui proiect IoT sau de robotică.