Tworzymy nowy pusty ROSject na platformie The Construct, wersja ROS Noetic.
mkdir catkin_ws
(lub o dowolnej innej nazwie)cd catkin_ws
mkdir src
(musi istnieć, żeby catkin zadziałał)catkin_make
- inicjalizuje pusty workspacesource ./devel/setup.bash
- inicjalizujemy środowisko naszego workspacea.
Uwaga: Przed uruchomieniem catkina, musimy mieć zainicjalizowane środowisko ROSowe w nasym terminalu za pomocą source /opt/ros/noetic/setup.bash
. The Construct robi to za nas. Jeśli mamy ROSa na własnym komputerze, możemy dodać tą linię do .bashrc
.
Czytaj więcej: Creating a workspace for catkin.
catkin_create_pkg basics rospy std_msgs actionlib_msgs message_generation
basics
- nazwa nowej paczki,rospy
- dependencje do Pythona,std_msgs
- dependencja do standardowych wiadomości ROSowych,actionlib_msgs
- dependencja do akcji,message_generation
- dependencja do generowania nowych wiadomości i serwisów.
W katalogu catkin_ws/src/basics
pojawią się pliki:
package.xml
- plik z informacjami o paczce (np. nazwa, wersja, autorzy, maintainerzy, opis, dependencje z innych paczek),CMakeLists.txt
- plik opisujący budowanie/instalowanie paczki.
Uwaga: jeśli dependencje w package.xml
są błędne, a w CMakeLists.txt
poprawne, to paczką zbuduję się u nas poprawnie, ale pojawią się problemy podczas publikowania paczki.
Node, czyli po prostu program, który jest widziany przez ROSa i może korzystać z jego dobrodziejstw.
-
Tworzymy plik
node.py
w katalogucatkin_ws/src/basics/src
:#!/usr/bin/env python3 import rospy def main(): rospy.init_node("node") rospy.loginfo("Hello World!") rospy.spin() if __name__ == "__main__": main()
-
Pamiętamy o shebangu (#!).
-
Ustawiamy uprawnienia do wykonywania pliku:
chmod +x node.py
-
Uruchamiamy node:
rosrun basics node.py
-
Możemy skorzystać z poleceń:
rosnode list
rosnode info /node
aby dowiedzieć się więcej o uruchomionych w tle nodeach.
Uwaga: Żeby node mógł poprawnie działać, w tle musi zostać uruchomiony master za pomocą polecania roscore
. The Construct robi to za nas i od razu działa on w tle.
Czytaj więcej: ROS Nodes.
Topic, czyli sposób na przekazywanie wiadomości danego typu pomiędzy nodeami. Najczęściej wykorzystywane są do komunikacji w jedną stronę, szczególnie jeśli chcemy żeby wiele nodeów słuchało wiadomości (np. podczas strumieni danych z sensorów).
Na Lab 1 napisaliśmy już dwa nodey, jeden subskrybujący topic, a drugi publikujący na niego.
-
Dodajemy do paczki
basics
nodepublisher.py
:#!/usr/bin/env python3 import rospy from std_msgs.msg import String rospy.init_node("publisher") publisher = rospy.Publisher("hello_world", String, queue_size=10) r = rospy.Rate(10) while not rospy.is_shutdown(): publisher.publish("Hello World") r.sleep()
-
Dodajemy do paczki
basics
nodesubscriber.py
:#!/usr/bin/env python3 import rospy from std_msgs.msg import String def callback(msg): print(msg) rospy.init_node("subscriber") subscriber = rospy.Subscriber("hello_world", String, callback) rospy.spin()
-
Pamiętamy o nadaniu uprawnienień:
chmod +x publisher.py
chmod +x subscriber.py
-
Uruchamiamy nodey w dwóch terminalach:
rosrun basics publisher.py
rusrun basics subscriber.py
-
Listę topiców zarejestrowanych w danym momencie przez ROSa sprawdzamy poleceniem:
rostopic list
-
Aby dowiedzieć się więcej o jakimś topicu wpisujemy:
rostopic info /scan
-
Aby słuchać wiadomości na topicu korzystamy z:
rostopic echo /imu
rostopic echo -n1 /imu # tylko jeden message
rostopic echo /scan/header # tylko pole header
-
Aby publikować wiadomości na topic używamy:
rostopic pub /hello_world std_msgs/String "data: 'hello world'"
-
Listę dostępnych wiadomości sprawdzić możemy poleceniem:
rosmsg list
-
Aby dowiedzieć się o nich więcej korzystamy z:
rosmsg info /LaserScan
Uwaga: Tworzenie własnych wiadmości nie zostało opisane na zajęciach, ale jest bardzo zbliżone do tworzenia serwisów. Więcej informacji tutaj.
Czytaj więcej: Creating a ROS msg and srv, Writing a Simple Publisher and Subscriber Python.
Serwisy najczęściej wykorzystywane są do prostych interakcji składających się z zapytania i odpowiedzi (np. zapytanie o aktualny stan nodea, poproszenie o zmianę stanu nodea).
-
Tworzymy plik definiujący nowy serwis, umieścimy go w
catkin_ws/src/basics/srv
i nazwiemyWordCount.srv
:string words --- uint32 count
Pierwsza część to request, a druga to resonse.
-
Modyfikujemy
CMakeLists.txt
w naszej paczce (catkin_ws/src/basics/CMakeLists.txt
):-
# powinno już być dodane na etapie # catkin_create_catkin_pkg ... std_msgs message_generation find_package(catkin REQUIRED COMPONENTS # ... std_msgs message_generation )
-
add_service_files( FILES WordCount.srv # ... )
-
generate_messages( DEPENDENCIES std_msgs # ... )
-
-
Powinniśmy też dodać:
<build_depend>message_generation</build_depend>
w odpowiednie miejsce
package.xml
, ale dzięki wywołanym wcześniej poleceniucatkin_create_catkin_pkg ... message_generation ...
zostało to już dodane.
-
Budujemy paczkę:
catkin_make
-
Sourceujemy workspace:
source ./devel/setup.bash
-
Sprawdzamy dostępne definicja serwisów:
rossrv list # wszystkie widoczne
rossrv package basics # wszystkie widoczne w paczke
rossrv info basics/WordList # info o serwisie
-
Tworzymy nowy node, który będzie serwerem serwisu:
#!/usr/bin/env python3 import rospy from basics.srv import WordCount, WordCountResponse def count_words(request): return WordCountResponse(len(request.words.split())) rospy.init_node("service_server") service = rospy.Service("word_count", WordCount, count_words) rospy.spin()
-
Tworzymy nowy node, który będzie klientem serwisu:
#!/usr/bin/env python3 import rospy from basics.srv import WordCount import sys rospy.init_node("service_client") rospy.wait_for_service("word_count") word_counter = rospy.ServiceProxy("word_count", WordCount) words = ' '.join(sys.argv[1:]) word_count = word_counter(words) print(words, '->', word_count.count)
-
Pamiętamy o nadaniu uprawnień:
chmod +x service_client.py
chmod +x service_server.py
-
Korzystając z poleceń
rosrun ...
- uruchamia noderosservice list
- wypisuje wszystkie działające serwisyrosservice call ...
- wywołuje serwis (np.rosservice call /word_count "words: 'bit ros'"
)
możemy testować nasze programy.
Czytaj więcej: Creating a ROS msg and srv, Writing a Simple Service and Client (Python).
Akcje wykorzystywane są do bardziej złożonych interakcji. Mamy możliwość zadania goala, otrzymywania cyklicznego feedbacku, przerwania wykonywania goala. Po zakończeniu akcji otrzymujemy response. Wykonywanie akcji dzieli się na etapy, które odpowiadają swoim statusom (actionlib_msgs/GoalStatusArray
).
Poniżej przygotujemy bardzo prosty serwer i klient akcji. Nie skorzystamy ze wszystkich dobrodziejstw tego interfejsu.
-
Tworzymy plik definiujący nową akcję, umieścimy go w
catkin_ws/src/basics/actions
i nazwiemyTimer.action
:duration time_to_wait --- duration time_elapsed uint32 updates_sent --- duration time_elapsed duration time_remaining
Pierwsza część to goal, a druga to response, trzecia to cykliczny feedback.
-
Modyfikujemy
CMakeLists.txt
w naszej paczce (catkin_ws/src/basics/CMakeLists.txt
):-
# powinno już być dodane na etapie # catkin_create_catkin_pkg ... std_msgs message_generation find_package(catkin REQUIRED COMPONENTS # ... actionlib_msgs message_generation )
-
add_action_files( FILES Timer.action # ... )
-
generate_messages( DEPENDENCIES actionlib_msgs # ... )
-
-
Powinniśmy też dodać:
<build_depend>actionlib_msgs</build_depend> <exec_depend>actionlib_msgs</exec_depend>
w odpowiednie miejsce
package.xml
, ale dzięki wywołanym wcześniej poleceniucatkin_create_catkin_pkg ... actionlib_msgs ...
zostało to już dodane.
-
Budujemy paczkę:
catkin_make
-
Sourceujemy workspace:
source ./devel/setup.bash
-
Tworzymy nowy node, który będzie serwerem akcji:
#!/usr/bin/env python3 import rospy import time import actionlib from basics.msg import TimerAction, TimerGoal, TimerResult def do_timer(goal): start_time = time.time() rospy.sleep(goal.time_to_wait.to_sec()) result = TimerResult() result.time_elapsed = rospy.Duration.from_sec(time.time() - start_time) result.updates_sent = 0 server.set_succeeded(result) rospy.init_node('timer_action_server') server = actionlib.SimpleActionServer('timer', TimerAction, do_timer, False) server.start() rospy.spin()
-
Tworzymy nowy node, który będzie klientem akcji:
#!/usr/bin/env python3 import rospy import actionlib from basics.msg import TimerAction, TimerGoal, TimerResult rospy.init_node('timer_action_client') client = actionlib.SimpleActionClient('timer', TimerAction) client.wait_for_server() goal = TimerGoal() goal.time_to_wait = rospy.Duration.from_sec(5.0) client.send_goal(goal) client.wait_for_result() print('Time elapsed: %f' % (client.get_result().time_elapsed.to_sec()))
-
Korzystając z poleceń
rosrun ...
- uruchamia node,rostopic list
- wypisuje wszystkie działające topici,rostopic info
- wypisuje informację o topicu,rostopic pub ...
- wywołuje serwis,
możemy testować nasze programy.
Uwaga: Akcje są zbudwane na topicach, więc dlatego korzystamy z
rostopic
.
Czytaj więcej: actionlib
- Ładne diagramy
- ROS Wiki
- Programming Robots with ROS, O'Reilly Media - książka dotyczy starszej wersji ROSa.