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.
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.
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:
Noduri - Fiecărui proces îi corespunde un nod. De exemplu, pentru controlul motoarelor într-o aplicație IoT, vom avea un nod special pentru aceasta sau de exemplu un nod pentru procesarea informațiilor venite de la o cameră video.
ROS Master - Nodul principal prin care sunt trimise toate mesajele.
Topics - Reprezintă mecanismul de comunicare publish / subscriber. Un nod poate publica un topic sau poate să se înregistreze la cele de interes.
Services - Spre deosebire de topicuri, serviciile reprezintă un mecanism prin care se poate realiza o interacțiune request / reply. Comunicarea este în mod standard stateless dar pentru situațiile în care se dorește eliminarea timpului de conectare se pot realiza conexiuni persistente către servicii.
Limbajele de programare suportate oficial sunt:
C++: este folosit pentru realizarea de aplicații performante cu ajutorul librăriei roscpp
Python: în general, se folosește librăria Python rospy pentru a realiza rapid prototipurile, în timp ce C++ este pentru producție.
LISP: prin librăria roslisp
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
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
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.
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:
toggle_led()
va primi un parametru de tipul Int32 în funcție de valoarea căruia un led de pe placa Arduino se va aprinde și stinge;
go_left()
va aprinde ledul ;
go_right()
va stinge ledul.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.
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ă.
de Ioana Negruț