Zielgruppe: Schülerinnen und Schüler mit Grundkenntnissen in Python, die noch keine Erfahrung mit Linux-Terminals oder ROS haben.
Ziel: Am Ende dieses Tutorials hast du einen eigenen ROS2-Knoten gebaut, der einen simulierten Roboter bewegt.
- Linux-Grundlagen Das Terminal verstehen
- ROS2 Jazzy installieren
- Erste Schritte mit ROS2
- Ein ROS2-Paket erstellen
- Den Knoten programmieren
- Paket bauen mit Colcon
- Den Knoten testen mit echtem Simulator
- Bonus: JSON-Befehlsdateien
Bevor wir irgendetwas mit ROS machen können, müssen wir uns mit dem wichtigsten Werkzeug vertraut machen: dem Terminal (auch „Kommandozeile" oder „Shell" genannt).
Das Terminal ist kein Relikt aus der Vergangenheit. In der Robotik, Systemadministration und Softwareentwicklung ist es das wichtigste Arbeitsmittel. Mit etwas Übung wird es sich schnell selbstverständlich anfühlen.
Auf Ubuntu kannst du das Terminal öffnen mit:
- Tastenkombination:
Strg + Alt + T - Rechtsklick auf den Desktop → „Open Terminal"
- Im App-Menü nach „Terminal" suchen
Du siehst dann so etwas:
deinname@deincomputer:~$
Das ~ steht für dein Home-Verzeichnis (/home/deinname). Das $ zeigt an, dass du als normaler Benutzer eingeloggt bist.
pwdpwd steht für Print Working Directory – es zeigt dir, in welchem Ordner du dich gerade befindest.
ls
ls -l # Detailansicht (Größe, Datum, Berechtigungen)
ls -la # Auch versteckte Dateien anzeigen (beginnen mit .)cd Dokumente # In den Ordner "Dokumente" wechseln
cd .. # Einen Ordner nach oben
cd ~ # Direkt ins Home-Verzeichnis
cd /home/deinname/ros2_ws # Absoluter PfadTipp – Tab-Vervollständigung: Wenn du den Anfang eines Ordner- oder Dateinamens tippst und dann
Tabdrückst, vervollständigt das Terminal automatisch. Wenn es mehrere Möglichkeiten gibt, drückeTabzweimal und alle Optionen werden angezeigt. Das spart enorm viel Zeit.
mkdir mein_ordner
mkdir -p pfad/zu/tiefem/ordner # Erstellt alle Zwischenordnerrm datei.txt # Datei löschen
rm -r ordner/ # Ordner (und Inhalt) löschen
rm -rf ordner/ # Erzwungen löschen – VORSICHT, kein Papierkorb!Achtung:
rmlöscht dauerhaft, ohne Rückfrage. Es gibt keinen Papierkorb im Terminal. Führe niemals Befehle aus dem Internet aus, die du nicht verstehst – vor allem solche, diesudo,rmund-rfin Kombination enthalten.
cat datei.txt # Inhalt ausgeben
nano datei.txt # Datei im Terminal-Editor öffnenNano ist ein einfacher Texteditor, der direkt im Terminal läuft. Die wichtigsten Tasten:
Strg + O→ Speichern (dannEnterbestätigen)Strg + X→ BeendenStrg + K→ Zeile ausschneidenStrg + W→ Suchen
Manche Befehle brauchen Administratorrechte. Dafür gibt es sudo (Super User Do):
sudo apt install irgendwasHinweis: Wenn du nach deinem Passwort gefragt wirst, siehst du beim Tippen gar nichts – keine Sterne, keine Punkte. Das ist normal und beabsichtigt. Tippe dein Passwort und drücke
Enter.
Wenn ein Programm im Terminal läuft und du es stoppen möchtest:
Strg + C– Programm sofort beenden. Diesen Shortcut wirst du sehr häufig verwenden.
Das normale Strg + C / Strg + V funktioniert im Terminal nicht, weil Strg + C ja Prozesse beendet. Stattdessen:
- Kopieren: Text markieren →
Strg + Shift + C - Einfügen:
Strg + Shift + V
Im Laufe dieses Tutorials wirst du mehrere Terminal-Fenster gleichzeitig brauchen. Du kannst beliebig viele Fenster öffnen und nebeneinander anordnen.
| Befehl | Bedeutung |
|---|---|
pwd |
Aktuellen Pfad anzeigen |
ls |
Verzeichnisinhalt auflisten |
cd <ordner> |
Verzeichnis wechseln |
mkdir <name> |
Ordner erstellen |
rm <datei> |
Datei löschen |
cat <datei> |
Dateiinhalt anzeigen |
nano <datei> |
Datei bearbeiten |
Tab |
Autovervollständigung |
Strg + C |
Programm beenden |
Strg + Shift + V |
Einfügen im Terminal |
sudo <befehl> |
Als Administrator ausführen |
ROS steht für Robot Operating System – ist aber kein Betriebssystem im klassischen Sinne, sondern ein Framework: ein Gerüst aus Werkzeugen, Bibliotheken und Konventionen, das die Entwicklung von Robotersoftware massiv vereinfacht.
Stell dir vor, du baust einen Roboter. Er hat Sensoren (Kamera, Lidar, GPS), Aktoren (Motoren, Greifer) und braucht eine Software, die alles koordiniert. ROS gibt dir dafür:
- einen standardisierten Weg, wie verschiedene Programmteile miteinander kommunizieren
- fertige Treiber für Hunderte von Sensoren und Motoren
- Werkzeuge zum Visualisieren, Debuggen und Aufzeichnen von Daten
- eine große Community und viele fertige Pakete
ROS2 Jazzy ist die aktuelle stabile Version (Stand 2024/2025), optimiert für Ubuntu 24.04.
Die Installation von ROS2 ist normalerweise ein aufwendiger Prozess mit vielen manuellen Schritten. Wir verwenden ein Installations-Skript, das alles automatisch erledigt.
Öffne ein Terminal und führe folgenden Befehl aus (alles in einer Zeile, oder kopiere den Block wie er ist):
curl -fsSL https://raw.githubusercontent.com/Merlin2LmmL/ROS2-Jazzy-Install-Script/refs/heads/main/ros2-jazzy-installer.sh \
-o ros2-jazzy-installer.sh \
&& chmod +x ros2-jazzy-installer.sh \
&& ./ros2-jazzy-installer.shWas dieser Befehl tut:
curl -fsSL ...lädt das Skript herunter-o ros2-jazzy-installer.shspeichert es unter diesem Namenchmod +xmacht es ausführbar (vergleichbar mit einer.exeunter Windows)./ros2-jazzy-installer.shführt es aus
Die Installation dauert je nach Internetgeschwindigkeit und Rechner 2–10 Minuten.
Nach der Installation brauchen wir einen Workspace – den Hauptordner, in dem alle deine ROS2-Projekte liegen werden.
mkdir -p ~/ros2_ws/src
cd ~/ros2_wsDas ~ ist eine Abkürzung für /home/deinbenutzername. Du kannst den Workspace auch anders nennen, z.B. ros2_jazzy_ws.
ROS2 stellt viele Befehle und Umgebungsvariablen bereit. Damit das Terminal diese kennt, muss die ROS2-Umgebung gesourct (aktiviert) werden:
source /opt/ros/jazzy/setup.bashTipp: Du musst das in jedem neuen Terminal-Fenster wiederholen. Um das zu automatisieren, füge diese Zeile zur Datei
~/.bashrchinzu (diese Datei wird bei jedem neuen Terminal automatisch ausgeführt):echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc source ~/.bashrc
Teste die Installation mit:
ros2 --help
// ODER
echo $ROS_DISTROMit ersterem sollte dir Hilfe zu den Befehlen mit ros2 gegeben werden und mit zweiterem solltest etwas wie jazzy sehen.
ROS2 basiert auf einem einfachen, aber mächtigen Konzept:
[Node A] -- publiziert Nachrichten --> [Topic] -- empfängt Nachrichten --> [Node B]
- Node (Knoten): Ein eigenständiges Programm, das eine bestimmte Aufgabe erfüllt (z.B. einen Motor ansteuern, Kamerabilder verarbeiten, Entscheidungen treffen)
- Topic: Ein benannter Kommunikationskanal. Nodes können Topics publizieren (senden) oder subscriben (empfangen). Stell es dir wie einen gemeinsamen "Chat" vor, auf den verschiedene Knoten Zugriff haben.
- Message: Das Datenformat, das über ein Topic geschickt wird (z.B. eine Geschwindigkeit, ein Bild, ein Sensorwert). Stell es dir wie die Sprache im Chat vor.
Das Prinzip ist ähnlich wie ein Schwarzes Brett: Jeder kann Zettel anpinnen (publizieren) und jeder kann Zettel lesen (subscriben). Das Schwarze Brett selbst ist das Topic.
ros2 --help # Alle verfügbaren Unterbefehle anzeigen
ros2 node list # Alle laufenden Nodes anzeigen
ros2 topic list # Alle aktiven Topics anzeigen
ros2 topic echo /topic # Nachrichten auf einem Topic live anzeigen
ros2 topic info /topic # Infos über ein TopicTurtlesim ist ein kleines Demoprogramm, das mit ROS2 mitgeliefert wird: eine Schildkröte auf dem Bildschirm, die du über ROS2-Topics steuern kannst. Es eignet sich gut zum ersten Kennenlernen.
Du brauchst drei Terminal-Fenster gleichzeitig:
Terminal 1 – Simulation starten:
ros2 run turtlesim turtlesim_nodeTerminal 2 – Steuerung starten:
ros2 run turtlesim turtle_teleop_keyKlicke in Terminal 2 und steuere die Schildkröte mit den Pfeiltasten.
Terminal 3 – Nachschauen, was passiert:
ros2 topic listDu siehst mehrere Topics, unter anderem /turtle1/cmd_vel. Das ist der Kanal, über den Bewegungsbefehle gesendet werden.
Jetzt sieh dir an, was gesendet wird, wenn du die Schildkröte steuerst:
ros2 topic echo /turtle1/cmd_velDu wirst Ausgaben wie diese sehen:
linear:
x: 2.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0
Das ist eine Twist-Message – das Standardformat für Geschwindigkeitsbefehle in ROS2. linear.x ist die Vorwärtsbewegung, angular.z ist die Drehung. Dieses Format werden wir gleich selbst verwenden.
In ROS2 wird Code in Paketen organisiert. Ein Paket ist ein strukturierter Ordner mit deinem Code plus Metadaten (Name, Abhängigkeiten, Lizenz). Pakete haben mehrere Vorteile:
- Einfaches Teilen und Wiederverwenden
- Standardisierte Struktur
- Automatische Abhängigkeitsverwaltung
ros2 runkann deine Nodes einfach finden
Navigiere zunächst in den src-Ordner deines Workspaces:
cd ~/ros2_ws/srcDann erstelle dein Paket. Ersetze die Platzhalter <...> mit deinen eigenen Angaben:
ros2 pkg create <paketname> \
--build-type ament_python \
--dependencies rclpy geometry_msgs std_msgs \
--license MIT \
--maintainer-name "<Dein Name>" \
--maintainer-email "<deine@email.de>" \
--description "<Beschreibe, was dein Paket macht>" \
--node-name <node_name>Beispiel (verwende deine eigenen Angaben):
ros2 pkg create mein_roboter_paket \
--build-type ament_python \
--dependencies rclpy geometry_msgs std_msgs \
--license MIT \
--maintainer-name "Max Mustermann" \
--maintainer-email "max@beispiel.de" \
--description "Mein erster ROS2-Knoten, der einen Roboter bewegt." \
--node-name bewegungssteuerungPaket- und Knotennamen sollten nur Kleinbuchstaben, Zahlen und Unterstriche enthalten – keine Leerzeichen, keine Umlaute.
Wechsle in dein neues Paket und schau dir die Struktur an:
cd ~/ros2_ws/src/<paketname>
ls -laSolche Verzeichnisbäume lassen sich auch direkt ausgeben mit dem Befehl tree -L n, wobei n die maximale Tiefe angibt:
<paketname>/
├── package.xml <- Metadaten (Name, Abhängigkeiten, Lizenz)
├── resource/
│ └── <paketname> <- Marker-Datei für ROS2
├── setup.cfg <- Konfiguration für ros2 run
├── setup.py <- Python-Installationsanleitung
└── <paketname>/
├── __init__.py <- Macht den Ordner zu einem Python-Paket
└── <node_name>.py <- Hier kommt dein Code!
Öffne die Node-Datei und schau sie dir an:
nano <paketname>/<node_name>.pyDa steht schon etwas Boilerplate-Code. Wir werden diese Datei jetzt komplett neu befüllen.
Dies ist das Herzstück des Tutorials. Wir schreiben einen ROS2-Knoten, der eine Sequenz von Bewegungsbefehlen ausführt – wie ein kleines Programm, das dem Roboter sagt: „Fahre 2 Sekunden geradeaus, drehe dich, fahre nochmals geradeaus, stoppe."
Bevor wir programmieren, planen wir kurz. Unser Knoten soll:
- Eine Liste von Bewegungsanweisungen enthalten (z.B. „fahre 2 Sek. vorwärts, dann drehe dich 1,5 Sek.")
- Das Ziel-Topic über einen konfigurierbaren Parameter entgegennehmen
- Die Anweisungen nacheinander ausführen
- Dafür Nachrichten an das konfigurierte Topic schicken
- Am Ende stoppen
Eine Twist-Nachricht hat zwei Teile:
linear: Geschwindigkeit in x (vorwärts/rückwärts), y (seitwärts), z (hoch/runter) in Meter pro Sekundeangular: Drehrate um x (nicken), y (rollen), z (drehen) in Radiant pro Sekunde
Für die meisten Bodenroboter sind nur linear.x (Vorwärtsfahrt) und angular.z (Drehen) relevant:
linear.x > 0 -> vorwärts
linear.x < 0 -> rückwärts
angular.z > 0 -> Drehung links (gegen den Uhrzeigersinn)
angular.z < 0 -> Drehung rechts (im Uhrzeigersinn)
rclpy ist die offizielle Python-Bibliothek für ROS2. Du importierst sie, um ROS2 zu initialisieren, Nodes zu erstellen, Publisher anzulegen, Nachrichten zu verschicken und den Logger zu benutzen.
Weiterführende Dokumentation:
In ROS2 können Nodes Parameter deklarieren – Einstellungen, die beim Start des Nodes von außen übergeben werden können, ohne den Code zu ändern. Das ist nützlich für Dinge wie das Ziel-Topic.
Ein Parameter wird so deklariert und gelesen:
self.declare_parameter("mein_parameter", "standardwert")
wert = self.get_parameter("mein_parameter").get_parameter_value().string_valueBeim Start des Nodes übergibt man den Wert mit:
ros2 run <paket> <node> --ros-args -p mein_parameter:=wertÖffne die Datei:
nano ~/ros2_ws/src/<paketname>/<paketname>/<node_name>.pyErsetze den gesamten Inhalt mit folgendem Grundgerüst. Es enthält bewusst Lücken (markiert mit ???), die du schrittweise füllen sollst.
#!/usr/bin/env python3
"""
Bewegungssteuerung – ROS2 Node
================================
Führt eine Sequenz von Bewegungsanweisungen aus,
indem Nachrichten an ein konfigurierbares cmd_vel-Topic publiziert werden.
"""
import time
import sys
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
# =============================================================================
# AUFGABE 1: Bewegungsanweisungen definieren
# =============================================================================
#
# Jede Anweisung ist ein Dictionary mit folgenden Schlüsseln:
#
# "aktion" (str) : Beschreibender Name – nur für die Log-Ausgabe
# "linear_x" (float) : Vorwärts-Geschwindigkeit in m/s (negativ = rückwärts)
# "angular_z" (float) : Drehgeschwindigkeit in rad/s (positiv = links)
# "dauer" (float) : Wie lange diese Anweisung ausgeführt wird (in Sekunden)
#
# Hinweis: Die Turtlesim-Schildkröte nutzt Werte bis ca. 2.0 m/s.
# Ein echter Roboter fährt je nach Modell 0.1 bis 1.0 m/s.
#
# Mindestanforderung: Definiere mindestens 4 sinnvolle Anweisungen.
#
ANWEISUNGEN = [
# TODO: Füge hier deine eigenen Anweisungen ein!
{"aktion": "vorwaerts", "linear_x": ???, "angular_z": ???, "dauer": ???},
{"aktion": "links", "linear_x": ???, "angular_z": ???, "dauer": ???},
{"aktion": "stop", "linear_x": 0.0, "angular_z": 0.0, "dauer": 0.5},
]
class BewegungsNode(Node):
"""
ROS2-Node, der Bewegungsanweisungen sequenziell ausführt.
Erbt von rclpy.node.Node – das macht sie zu einem vollwertigen ROS2-Knoten.
"""
# Wie oft pro Sekunde wird eine Nachricht gesendet? (Frequenz in Hz)
PUBLISH_FREQUENZ = 10
def __init__(self):
# -----------------------------------------------------------------------
# AUFGABE 2: Node benennen
# -----------------------------------------------------------------------
# Der Node braucht einen eindeutigen Namen. Dieser Name erscheint
# in `ros2 node list`. Übergib ihn an super().__init__().
#
# Dokumentation: https://docs.ros2.org/latest/api/rclpy/api/node.html
#
super().__init__("???") # TODO: Gib deinem Node einen sinnvollen Namen!
self.get_logger().info(
f"Node gestartet. {len(ANWEISUNGEN)} Anweisung(en) geladen."
)
# -----------------------------------------------------------------------
# AUFGABE 3: Ziel-Topic per Parameter konfigurierbar machen
# -----------------------------------------------------------------------
# Unterschiedliche Simulatoren und echte Roboter verwenden unterschied-
# liche Topic-Namen für Bewegungsbefehle. Damit wir den Node flexibel
# einsetzen können, soll das Topic nicht fest im Code stehen, sondern
# über einen ROS2-Parameter übergeben werden.
#
# Gehe wie folgt vor:
# 1. Deklariere einen Parameter "cmd_vel_topic" mit dem Standardwert
# "/cmd_vel" (wird genutzt, wenn nichts übergeben wird).
# 2. Lies den Wert des Parameters aus.
# 3. Gib das verwendete Topic per Log-Nachricht aus, damit der Nutzer
# beim Start sieht, welches Topic verwendet wird.
#
# Syntax zum Deklarieren und Lesen eines String-Parameters:
# self.declare_parameter("parametername", "standardwert")
# wert = self.get_parameter("parametername").get_parameter_value().string_value
#
# Dokumentation:
# https://docs.ros2.org/latest/api/rclpy/api/node.html#rclpy.node.Node.declare_parameter
#
self.declare_parameter("cmd_vel_topic", ???) # TODO
self._topic = self.get_parameter(???).get_parameter_value().string_value # TODO
self.get_logger().info(f"Verwende Topic: {???}") # TODO
# -----------------------------------------------------------------------
# AUFGABE 4: Publisher erstellen
# -----------------------------------------------------------------------
# Ein Publisher sendet Nachrichten an ein bestimmtes Topic.
# Verwende jetzt self._topic als Topic-Namen (nicht den hartkodierten
# String "/cmd_vel"), damit der Parameter aus Aufgabe 3 tatsächlich
# genutzt wird.
#
# Syntax:
# self.create_publisher(<NachrichtenTyp>, "<topic_name>", <queue_size>)
#
# NachrichtenTyp : Twist (aus geometry_msgs.msg)
# topic_name : self._topic
# queue_size : 10
#
# Dokumentation:
# https://docs.ros2.org/latest/api/rclpy/api/node.html#rclpy.node.Node.create_publisher
#
self._publisher = self.create_publisher(???, ???, ???) # TODO
# Starte die Ausführung 0.5 Sekunden nach dem Start,
# damit der Node vollständig initialisiert ist.
self._timer = self.create_timer(0.5, self._starte_ausfuehrung)
# ---------------------------------------------------------------------------
# AUFGABE 5: Twist-Nachricht erstellen
# ---------------------------------------------------------------------------
def _erstelle_twist(self, anweisung: dict) -> Twist:
"""
Wandelt ein Anweisungs-Dictionary in eine Twist-Nachricht um.
Args:
anweisung: Dictionary mit Schlüsseln wie "linear_x", "angular_z"
Returns:
Eine Twist-Nachricht, bereit zum Publizieren
"""
# Eine Twist-Nachricht hat folgende Felder:
#
# msg.linear.x (float) <- Vorwärtsgeschwindigkeit
# msg.linear.y (float) <- Seitwärtsgeschwindigkeit (meist 0)
# msg.linear.z (float) <- Vertikalgeschwindigkeit (meist 0)
# msg.angular.x (float) <- Nickrate (meist 0)
# msg.angular.y (float) <- Rollrate (meist 0)
# msg.angular.z (float) <- Drehrate links/rechts
#
# Tipp: Verwende .get("schluessel", standardwert) – falls ein Schlüssel
# im Dictionary fehlt, wird der Standardwert verwendet.
# Beispiel: anweisung.get("linear_x", 0.0)
#
msg = Twist()
msg.linear.x = ??? # TODO
msg.angular.z = ??? # TODO
# Alle anderen Felder bleiben 0.0 (Standardwert bei Twist)
return msg
# ---------------------------------------------------------------------------
# AUFGABE 6: Einzelne Anweisung ausführen
# ---------------------------------------------------------------------------
def _fuehre_anweisung_aus(self, index: int, anweisung: dict) -> None:
"""
Führt eine einzelne Anweisung für die angegebene Dauer aus.
Args:
index : Position der Anweisung in der Liste (für die Anzeige)
anweisung: Das Anweisungs-Dictionary
"""
# Diese Funktion soll:
# 1. Die Werte aus dem Dictionary lesen (Aktion, Dauer, Geschwindigkeiten)
# 2. Eine Log-Nachricht ausgeben, welche Anweisung gerade läuft
# 3. Die Twist-Nachricht erstellen (benutze _erstelle_twist!)
# 4. Die Nachricht für die angegebene Dauer wiederholt publizieren
#
# Tipp für Schritt 4 – Zeitschleife:
# endzeit = time.time() + dauer
# intervall = 1.0 / self.PUBLISH_FREQUENZ # z.B. 0.1 s bei 10 Hz
# while time.time() < endzeit:
# self._publisher.publish(nachricht)
# time.sleep(intervall)
#
# Nützliche Methoden:
# self.get_logger().info("Nachricht") <- Ausgabe im Terminal
# self._publisher.publish(nachricht) <- Nachricht senden
# time.sleep(sekunden) <- Kurz warten
#
aktion = anweisung.get("aktion", f"schritt_{index}")
dauer = float(anweisung["dauer"])
self.get_logger().info(???) # TODO: z.B. "[1/5] 'vorwaerts' für 2.0s"
twist = ??? # TODO: Twist erstellen
# TODO: Nachricht für "dauer" Sekunden in einer Schleife publizieren
# ...
# ---------------------------------------------------------------------------
# AUFGABE 7: Stopp-Befehl senden
# ---------------------------------------------------------------------------
def _sende_stopp(self) -> None:
"""
Sendet einen Stopp-Befehl (alle Geschwindigkeiten auf 0).
Ohne diesen Befehl fährt ein echter Roboter weiter!
"""
# Eine leere Twist-Nachricht hat automatisch alle Felder auf 0.0.
# Erstelle eine solche Nachricht und publiziere sie.
#
self._publisher.publish(???) # TODO
self.get_logger().info("Stopp-Befehl gesendet (alle Geschwindigkeiten = 0).")
# ---------------------------------------------------------------------------
# AUFGABE 8: Alle Anweisungen der Reihe nach ausführen
# ---------------------------------------------------------------------------
def _starte_ausfuehrung(self) -> None:
"""
Wird einmalig nach dem Node-Start aufgerufen.
Führt alle Anweisungen der Reihe nach aus.
"""
self._timer.cancel() # Einmaliger Timer wird nicht mehr gebraucht
try:
# Iteriere über alle Anweisungen in ANWEISUNGEN und rufe
# für jede _fuehre_anweisung_aus() auf.
#
# Tipp: enumerate() gibt dir gleichzeitig Index und Wert:
# for i, anweisung in enumerate(ANWEISUNGEN):
# ...
#
# TODO: Schleife über alle Anweisungen!
self.get_logger().info("Alle Anweisungen abgeschlossen!")
except KeyboardInterrupt:
self.get_logger().info("Durch Benutzer unterbrochen.")
finally:
# Immer stoppen am Ende – egal ob fertig oder unterbrochen!
self._sende_stopp()
rclpy.shutdown()
# =============================================================================
# Einstiegspunkt – hier startet das Programm
# =============================================================================
def main(args=None) -> None:
"""Initialisiert ROS2 und startet den Node."""
rclpy.init(args=args)
node = BewegungsNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
if rclpy.ok():
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()| Nr. | Aufgabe | Was du tun musst |
|---|---|---|
| 1 | ANWEISUNGEN befüllen |
Mindestens 4 sinnvolle Bewegungsanweisungen definieren |
| 2 | Node benennen | super().__init__("dein_node_name") |
| 3 | Topic-Parameter anlegen | Parameter deklarieren, auslesen und im Log ausgeben |
| 4 | Publisher erstellen | self.create_publisher(Twist, self._topic, 10) |
| 5 | Twist-Nachricht befüllen | Felder linear.x und angular.z aus dem Dictionary setzen |
| 6 | Anweisung ausführen | Log-Ausgabe, Twist erstellen, Zeitschleife |
| 7 | Stopp senden | Leere Twist()-Nachricht publizieren |
| 8 | Alle Anweisungen iterieren | for i, a in enumerate(ANWEISUNGEN): |
Falls du bei einzelnen Aufgaben nicht weiterkommst, findest du hier Bausteine als Orientierung – ohne direkte Lösungen.
Log-Nachricht mit Positionsangabe:
self.get_logger().info(
f"[{index + 1}/{len(ANWEISUNGEN)}] Führe '{aktion}' aus für {dauer:.1f}s"
)Zeitschleife zum wiederholten Publizieren:
endzeit = time.time() + dauer
intervall = 1.0 / self.PUBLISH_FREQUENZ # z.B. 0.1 s bei 10 Hz
while time.time() < endzeit:
self._publisher.publish(nachricht)
time.sleep(intervall)Was enumerate() macht:
früchte = ["Apfel", "Banane", "Kirsche"]
for i, frucht in enumerate(früchte):
print(f"{i}: {frucht}")
# Ausgabe:
# 0: Apfel
# 1: Banane
# 2: KirscheDu musst nicht alle Aufgaben fertig haben, um zu testen. Sobald die Aufgaben 1–4 erledigt sind, kannst du das Paket bauen und mit ros2 topic echo /cmd_vel prüfen, ob dein Publisher Nachrichten sendet. Dafür braucht es noch keinen laufenden Simulator.
Wenn du alles fertig hast und mehr ausprobieren möchtest:
- Detaillierteres Logging: Gib in der Schleife die verbleibende Zeit mit aus
- Validierung: Was passiert, wenn eine Anweisung kein
dauer-Feld hat? Fange das ab - JSON-Unterstützung: Siehe Kapitel 8
Colcon ist das Build-System von ROS2. Es installiert deine Pakete in eine standardisierte Verzeichnisstruktur, sodass ros2 run und andere ROS2-Werkzeuge deine Nodes finden können.
Auch wenn Python-Code nicht wirklich kompiliert wird, braucht ROS2 den Build-Schritt trotzdem:
- Symbolische Links und Konfigurationsdateien werden erstellt
- Entry Points werden registriert (damit
ros2 rundeine Node findet) - Abhängigkeiten werden geprüft
Wichtig: Führe
colcon buildimmer aus dem Wurzelverzeichnis deines Workspaces aus (~/ros2_ws), nicht aus einem Unterordner. Sonst entstehen die Ordnerbuild/,install/undlog/an der falschen Stelle.
cd ~/ros2_wsWenn du später einmal mehrere Pakete selbst gebaut hast, ist es sinnvoll, nur das Paket zu bauen, an dem du aktuell arbeitest. Nutze dafür die flag --packages-select. Aber Achtung: Du kannst dir hier deinen Paketnamen nicht vervollständigen lassen:
colcon build --packages-select <paketname>Oder alles bauen:
colcon buildEine erfolgreiche Ausgabe sieht so aus:
Starting >>> <paketname>
Finished <<< <paketname> [0.66s]
Summary: 1 package finished [0.77s]
Häufige Fehler:
SyntaxError: Python-Syntaxfehler – öffne die Datei und prüfe die angegebene ZeileModuleNotFoundError: Eine Abhängigkeit fehlt – prüfepackage.xmlundsetup.pyKeyError: Ein Dictionary-Schlüssel fehlt – prüfe deineANWEISUNGEN
Nach jedem Build muss die Umgebung neu gesourct werden, damit ROS2 dein frisch gebautes Paket findet:
source install/setup.bashTipp: Du kannst das automatisieren. Füge diese Zeile zur
~/.bashrchinzu:source ~/ros2_ws/install/setup.bash
Öffne zwei Terminal-Fenster:
Terminal 1 – Node starten:
cd ~/ros2_ws
source install/setup.bash
ros2 run <paketname> <node_name>Terminal 2 – Nachrichten überwachen:
ros2 topic echo /cmd_velWenn alles korrekt ist, siehst du in Terminal 2 Nachrichten wie:
linear:
x: 0.3
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0Die OHM Technische Hochschule Nürnberg hat einen 2D-Robotersimulator entwickelt, der sich gut aufgrund seiner Einfachheit für unsere Zwecke eignet. Wir installieren ihn als weiteres ROS2-Paket:
# 1. Quellcode herunterladen
cd ~/ros2_ws/src
git clone --branch ros2 https://github.com/autonohm/ohm_mecanum_sim.git
# 2. Workspace bauen
cd ~/ros2_ws
colcon build --symlink-install
# 3. Umgebung sourcen
source install/setup.bash
# 4. Pygame installieren (das Grafikframework für den Simulator)
pip3 install pygame --break-system-packagesAnmerkung: Die flag "--break-system-packages" klingt sehr gefährlich, ist sie aber in diesem Fall gar nicht. Sie wurde beabsichtigt so abschreckend benannt, um vor dem Gebrauch abzuschrecken, da dringend empfohlen wird, pip Pakete in einem sog. venv zu installieren. Da das Einrichten eines Venvs allerdings 1-2 extra Kapitel erfordern würde, weichen wir aus Zeit- und Komplexitätsgründen darauf zurück, die Library global auf dem Rechner zu installieren. Falls jemand dagegen einen Einwand haben sollte, steht es demjenigen frei, stattdessen Pycharm in einem Venv zu installieren.
Starte den Simulator:
ros2 run ohm_mecanum_sim ohm_mecanum_sim_nodeEin Fenster mit dem Roboter öffnet sich. Jetzt finden wir heraus, welches Topic der Simulator für Bewegungsbefehle verwendet:
ros2 topic listSchau dir die Liste an. Welches Topic enthält cmd_vel? Es ist nicht einfach /cmd_vel.
Du kannst auch mehr Informationen über ein Topic bekommen:
ros2 topic info /<topic_name>Sobald du das richtige Topic kennst, starte alles in zwei (oder drei) Terminals:
Terminal 1 – Node mit dem richtigen Topic starten:
cd ~/ros2_ws
source install/setup.bash
ros2 run <paketname> <node_name> --ros-args \
-p cmd_vel_topic:=/<das_richtige_topic>Da du in Aufgabe 3 den cmd_vel_topic-Parameter implementiert hast, kannst du das Ziel-Topic jetzt beim Start bequem von außen übergeben – ohne den Code anfassen zu müssen.
Terminal 2 – Der Simulator:
ros2 run ohm_mecanum_sim ohm_mecanum_sim_nodeTerminal 3 (optional) – Topic überwachen:
ros2 topic echo /<das_richtige_topic>Wenn alles klappt, siehst du den Roboter im Simulator-Fenster die Bewegungen aus deiner ANWEISUNGEN-Liste ausführen.
Bisher sind die Anweisungen direkt im Python-Code eingebaut. Das ist unpraktisch: Jedes Mal, wenn wir die Route ändern wollen, müssen wir den Code editieren und neu bauen.
Eine elegantere Lösung: Die Anweisungen in einer JSON-Datei speichern und diese dem Node übergeben.
JSON (JavaScript Object Notation) ist ein einfaches Textformat für strukturierte Daten. Es sieht Python-Dictionaries sehr ähnlich:
[
{"aktion": "vorwaerts", "linear_x": 0.3, "angular_z": 0.0, "dauer": 2.0},
{"aktion": "links", "linear_x": 0.0, "angular_z": 0.5, "dauer": 1.5},
{"aktion": "vorwaerts", "linear_x": 0.3, "angular_z": 0.0, "dauer": 2.0},
{"aktion": "stopp", "linear_x": 0.0, "angular_z": 0.0, "dauer": 0.5}
]Speichere das in einer Datei, z.B. ~/meine_route.json.
import json
with open("/pfad/zur/datei.json", "r") as f:
anweisungen = json.load(f)json.load() wandelt die JSON-Datei automatisch in Python-Dictionaries um – kein weiterer Aufwand.
Erweitere deinen Node so, dass er wahlweise die eingebauten ANWEISUNGEN verwendet, oder eine JSON-Datei lädt, wenn ein Dateipfad angegeben wird.
Tipp: Nutze einen zweiten ROS2-Parameter:
self.declare_parameter("instructions_file", "")
datei_pfad = self.get_parameter("instructions_file").get_parameter_value().string_value
if datei_pfad:
# JSON-Datei laden
with open(datei_pfad, "r") as f:
anweisungen = json.load(f)
else:
# Eingebaute Anweisungen verwenden
anweisungen = ANWEISUNGENVerwendung dann so:
ros2 run <paketname> <node_name> --ros-args \
-p cmd_vel_topic:=/<das_richtige_topic> \
-p instructions_file:=~/meine_route.jsonDamit du nicht manuell JSON schreiben musst, steht ein grafischer Editor zur Verfügung:
Dort kannst du deine Route visuell zusammenstellen und als JSON-Datei herunterladen. Die heruntergeladene Datei findest du dann in ~/Downloads/.
Du hast erfolgreich:
- Das Linux-Terminal kennengelernt
- ROS2 Jazzy installiert
- Die Grundkonzepte von ROS2 verstanden (Nodes, Topics, Messages)
- Ein ROS2-Paket erstellt
- Deinen ersten eigenen ROS2-Knoten mit konfigurierbarem Topic-Parameter programmiert
- Das Paket gebaut und getestet
- Einen Roboter-Simulator gesteuert
Das ist eine solide Grundlage für alles, was in der Robotik noch kommt. Mögliche nächste Schritte:
- Sensor-Daten empfangen (Subscribe auf Topics)
- Auf Sensordaten reagieren (z.B. stoppen, wenn ein Hindernis erkannt wird)
- Mehrere Nodes gleichzeitig laufen lassen und koordinieren
- ROS2 Launch-Files schreiben
ROS2-Umgebung wurde nicht gesourct:
source /opt/ros/jazzy/setup.bashNach dem Build die install-Umgebung nicht gesourct:
cd ~/ros2_ws
source install/setup.bash- Prüfe, ob das richtige Topic verwendet wird (
ros2 topic list) - Prüfe, ob der Node das Topic korrekt ausgibt (Log-Ausgabe beim Start)
- Prüfe, ob dein Node wirklich publiziert (
ros2 topic echo /<topic>) - Prüfe, ob der Node überhaupt läuft (
ros2 node list)
- Stelle sicher, dass du dich im richtigen Verzeichnis befindest (
~/ros2_ws) - Prüfe auf Python-Syntaxfehler in deiner
.py-Datei - Die Fehlermeldung zeigt meist genau die betroffene Zeile an
- Dein
_sende_stopp()-Aufruf fehlt oder funktioniert nicht - Prüfe, ob der
finally:-Block korrekt eingerückt ist
Tutorial erstellt für das Robotik-Wahlfach. Autor: Merlin Ortner ortnermerlin@gmail.com