Skip to content
LightrunnerwastakenPublic

About

Spotlab – eine Programmierumgebung für den Boston Dynamics Spot mit integriertem Code-Editor, 3D-Simulation und Steuerung des echten Roboters.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

spotlab

Python programmieren: vollständige API-Referenz mit allen 30 Methoden und 4 Properties, Einheiten und Backend-Grenzen. Beispiel-Abdeckung: was direkt geht und was das SDK benötigt. Neu: spot.look() für benannte Umgebungsrichtungen, state.x/y/heading/speed, spot.lights(), spot.beep(), spot.pose() und spot.supports().

Den Boston Dynamics Spot programmieren — für den Unterricht an der Kantonsschule.

spotlab nimmt die Betriebsmechanik des Spot-SDK ab (Anmeldung, Zeitsynchronisierung, Not-Aus, Lease, Leistungsverwaltung), damit ein Schüler mit fünf Zeilen Python anfangen kann. Es verstellt dabei nicht den Weg zum echten SDK: spot.robot und spot.send() führen jederzeit zum vollen bosdyn-Client.

Jeder Lauf wird vollständig aufgezeichnet — Kommandos, Roboterzustand mit 10 Hz, Bilder. Diese Aufzeichnungen sind zugleich die Datengrundlage, um später die MuJoCo-Simulation aus matura-spot gegen das echte Verhalten zu kalibrieren.

Installation

Schüler: Das Schüler-Release-ZIP herunterladen, entpacken und einrichten.cmd doppelklicken. Danach Spotlab über die Desktop-Verknüpfung öffnen und in der GUI programmieren. Das Paket enthält GUI und Simulation samt Robotermodell; das Matura-Repository und Entwicklerwerkzeuge werden nicht benötigt.

Schulanleitung · Releases bauen

Python 3.11–3.14 und einmalig Internetzugang werden vorausgesetzt. Die Pakete werden in einer eigenen .venv installiert; der Installer prüft Abhängigkeiten und den Modellaufbau. GitHubs „Source code“-Archiv ist kein Schüler-Release.

Entwickler: Im geklonten Repository:

.\einrichten.cmd -Entwickler
# Optional mit den lokalen Sim-Quellen:
.\einrichten.cmd -Entwickler -MitSim -SimPfad ..\matura-spot

Dieser ausdrückliche Modus installiert weiterhin dev, gui und mcp editierbar. Die Schülerausgabe installiert nur die GUI-/Sim-Abhängigkeiten. Die CLI bleibt für Fortgeschrittene verfügbar; für die Schüler ist kein Terminal im Alltag nötig.

Schnellstart

spotlab login                    # IP, Benutzer, Passwort (Passwort in den Windows-Tresor)
spotlab doctor                   # Netz, Anmeldung, Zeitsync, Not-Aus, Lease, Akku,
                                 # Lizenz, Nutzlasten, Dienste, Zertifikatsablauf
spotlab new mein-projekt
spotlab open mein-projekt
spotlab run hallo_spot.py

Ohne Roboter üben — baut und prüft alle Kommandos, bewegt nichts:

spotlab run hallo_spot.py --dryrun

Oberfläche

pip install -e .[gui]
spotlab gui

Elf Ansichten in einer Seitenleiste: Projekte (anlegen, in VS Code öffnen, starten — mit Häkchen für Trockenlauf), Code (der eingebaute Editor), Live-Lauf (Ereignisse, Telemetrie, Kamerabild, Ausgabe), Läufe (vergangene Läufe mit der Kurve kommandiertes gegen gemessenes Tempo), Karten (GraphNav aufzeichnen und ansehen), Umwelt (was Spot gerade sieht), Gehzeit (den Schulversuch starten und nachtragen), Fahren (den echten Spot über die Tastatur fahren), Raumeditor (Räume bauen und ohne Roboter fahren), Anbindungen (fremde Projekte) und Spot (Zugangsdaten und Prüfung). Hell und dunkel folgen der Windows-Einstellung.

Der eingebaute Editor

Die Ansicht Code ist eine Ergänzung, kein Ersatz — spotlab open und „In VS Code öffnen" bleiben. Sie kann: mehrere Reiter, Syntaxhervorhebung, Vervollständigung im ganzen Code, Syntaxfehler nach einer kurzen Ruhepause, Suchen und Ersetzen, Starten und Stoppen ohne die Ansicht zu wechseln, und anklickbare Dateinamen in Tracebacks — ein Klick springt an die Zeile.

Die Vervollständigung gilt überall, nicht nur nach spot.: nach jedem Punkt (math., eine Liste, eine eigene Variable) und sobald ein angefangenes Wort zwei Zeichen hat. Nach spot. und spotlab. steht zuerst die eigene Liste mit deutscher Erklärung und Signatur, darunter ergänzt jedi den Rest. In Kommentaren und Zeichenketten kommt nichts — dort schlägt jedi sonst Verzeichnisse deines Laptops vor. Strg+Leertaste fragt trotzdem, an jeder Stelle.

Zwei Dinge, die im Alltag zählen:

  • Vor dem Start wird alles gespeichert, nicht nur der sichtbare Reiter. Ein Projekt aus mehreren Dateien läuft sonst mit der alten Fassung seiner Importe.
  • Beide Editoren dürfen dieselbe Datei offen haben. Jeder Reiter merkt sich Änderungszeit und Grösse und fragt, bevor er fremde Änderungen überschreibt.

Nicht eingebaut: Debugger mit Haltepunkten, git-Integration, projektweite Suche, Erweiterungen. Dafür ist VS Code da.

Die Oberfläche zeigt jeden Lauf im Arbeitsordner — auch die, die du in VS Code mit F5 startest.

Zwei Arten anzuhalten, und der Unterschied ist wichtig:

Knopf Wirkung
Stopp Freundlich: das Programm bricht ab, der Abbau läuft, der Spot setzt sich hin. Reagiert es nach 3 s nicht, wird hartes Beenden angeboten.
NOT-AUS Hart, ein Klick, keine Rückfrage: der Prozess wird getötet, die Motoren gehen aus und der Spot sackt zusammen.

Der NOT-AUS wirkt nur auf das laufende spotlab-Programm. Das primäre Sicherheitsmittel bleibt der physische Not-Aus am Tablet.

Die GUI hält nie ein Lease — sie liest alles aus dem Lauf-Verzeichnis mit. Deshalb gibt es kein Live-Kamerabild ohne laufendes Skript: Bilder erscheinen, sobald ein Programm welche aufnimmt.

Karten

Der Spot kann einen Raum als GraphNav-Karte aufzeichnen und darauf danach autonom fahren.

Aufzeichnen — in der Ansicht „Karten" oder im Terminal:

spotlab record-map turnhalle

Zwei Voraussetzungen, die nicht verhandelbar sind: der Spot muss beim Start ein Fiducial sehen (die AprilTag-Markierung), und gefahren wird mit dem Tablet. spotlab zeichnet nur mit und übernimmt die Steuerung nicht — es braucht dafür kein Lease.

Fahre Runden. Beim Speichern sucht spotlab die Schleifen in der Aufnahme und schliesst sie, dann optimiert es die Anker; die Statuszeile sagt, was dabei herauskam. Ohne diesen Schritt ist eine Aufnahme eine Kette: der Dienst weiss nicht, dass der Gang, durch den Spot zum zweiten Mal fährt, derselbe ist, und legt einen zweiten Strang daneben. Spot fährt dann wie auf Schienen die aufgezeichnete Strecke ab, statt den kurzen Weg zu nehmen, und die Draufsicht schliesst sich sichtbar nicht. Beides passiert auf dem Roboter, ohne Lease, und scheitert es, wird die Karte trotzdem gespeichert — mit dem Grund in der Meldung.

Ansehen und auswählen — spotlab maps listet auf, die Ansicht „Karten" zeigt eine Draufsicht. Als aktive Karte setzen merkt die Wahl, danach reicht im Skript load_map() ohne Argument.

Darauf fahren:

with spotlab.connect() as spot:
    spot.power_on()
    spot.stand()

    karte = spot.load_map()          # die in der GUI gewählte Karte
    print(karte.waypoints)           # ['start', 'kueche', 'fenster']

    spot.localize()                  # über das Fiducial verorten
    spot.navigate_to("kueche")

navigate_to bewegt den Roboter autonom. Er fährt selbstständig eine Route ab. Sorge für freie Fläche und Aufsicht. Der Geschwindigkeitsdeckel aus config.toml gilt auch hier — er wird als velocity_limit an GraphNav durchgereicht.

Ohne Skript, aus dem Tab. In der Ansicht „Karten" eine Karte wählen, „🧭 Zu Wegpunkten fahren" drücken und in der Zeichnung einen Wegpunkt anklicken — Spot fährt hin. Die Zeichnung zeigt das Ziel als Ring, den Wegpunkt, an dem Spot sich verortet hat, gefüllt, und Spot selbst als Pfeil; die Zeile darunter sagt, was der Lauf tut. Ein Klick während der Fahrt wechselt das Ziel, derselbe Wegpunkt noch einmal geklickt heisst noch einmal fahren, „■ Stopp" hält an und beendet. Wegpunkte lassen sich hier auch nachträglich benennen: anklicken und „✎ Wegpunkt benennen…", oder Doppelklick — der Name landet im Graphen der Karte, wo ihn auch spot.navigate_to("kueche") liest; Namen müssen eindeutig sein, leer entfernt den Namen. Dahinter steckt kein zweiter Weg zum Roboter: der Knopf startet das Programm navigieren.py aus dem Projekt Beispiele am echten Spot, mit Lease, Not-Aus-Endpunkt, Aufzeichnung und dem Tempodeckel wie jeder Lauf; der Tab schreibt nur das Ziel in eine Datei des Lauf-Verzeichnisses und liest den Stand aus einer anderen. Zum Verorten muss ein AprilTag der Karte im Kamerabild sein — der Lauf versucht es alle zwei Sekunden neu und sagt, woran es hängt. Ein Ziel, das Spot nicht erreicht, beendet den Lauf nicht: der nächste Klick geht wieder.

Eine alte Karte nachträglich verbessern. Karten, die vor dieser Fassung aufgezeichnet wurden, sind Ketten. „✨ Karte verbessern…" im Tab „Karten" holt es nach: die Karte geht auf den Spot, dort werden Schleifen geschlossen und die Anker optimiert, und die verbesserte Karte kommt zurück. Spot bewegt sich dabei nicht, braucht aber das Lease — Hochladen gehört ihm. Deshalb ist es ein Programm (Beispiele/karte_verbessern.py) und keine Arbeit der Oberfläche; der Fortschritt steht in der Live-Ansicht. Geschrieben wird erst am Schluss und nur, wenn wirklich etwas gerechnet wurde: die gespeicherte Karte ist die einzige Kopie deiner Aufnahme.

Karten liegen im Format des SDK unter <arbeitsordner>/karten/<name>/. Eine mit spotlab aufgezeichnete Karte lässt sich deshalb unverändert an graph_nav_command_line.py und view_map.py aus dem Spot-SDK verfüttern — und umgekehrt.

Raumeditor — Räume bauen und ohne Roboter fahren

Der Sim fährt nach den am 12.08.2026 gemessenen Gangarten. Ein Meter dauert dort so lange wie am echten Spot — ein Programm, das im Übungsraum ankommt, kommt auch am Gerät an.

In der Ansicht Raumeditor wählst du ein Zimmer — eine Vorlage oder einen eigenen Raum —, setzt den Start und baust um, was du brauchst:

Vorlage Inhalt
leer vier Wände, ein Tag — für die ersten Schritte
moebliert Tisch, zwei Stuhlstapel, zwei Tags — Ausweichen üben
durchgang zwei Zimmer, eine Tür, der Tag liegt drüben
treppe ein Podest, eine Treppe mit sieben Stufen hinauf, der Tag hängt oben

Bauen. Links die Werkzeuge: Auswählen (klicken, ziehen, Rahmen aufziehen, Shift ergänzt), Wand (Klick-Klick zeichnet eine Wandkette, Esc beendet; Wandenden fangen sich), Block (Rechteck aufziehen), Boden (Rechteck aufziehen: ein Podest; mit anstieg wird es eine Rampe, mit anstieg und stufen eine Treppe — der Anstieg läuft entlang der langen Seite, ein Pfeil zeigt bergauf), Tag (Klick), Start (Klick setzt, Ziehen dreht den Blick). Am gewählten Element erscheinen Griffe: Wandenden, Block- und Bodenecken, ein Drehring. Rechts stehen die Elementliste, die Zahlenfelder (Mitte, Grösse, Drehung, Höhe, z …) und Hinweise. Blöcke lassen sich drehen und haben eine Höhe; in der Datei heissen sie [[block]], Böden [[boden]], die alte Schreibweise hindernisse wird weiter gelesen. Eigene Räume liegen unter <arbeitsordner>/raeume/<name>.toml; die Vorlagen bleiben unverändert, ein geänderter Raum fragt beim Speichern nach einem Namen.

Ebenen. Jedes Element hat ein z, seine Unterkante über dem Grundboden. Über der Zeichnung wählst du die Ebene (die Bodenhöhen des Raums, „alle Ebenen" zeigt alles): was auf einer anderen Ebene liegt, erscheint blass, neue Elemente landen auf der gewählten Ebene, und G gefolgt von Z hebt die Auswahl. Absturzkanten (mehr als eine Stufe, 0.25 m) zeichnet der Editor als Kammlinie; Spot bleibt dort stehen wie an einer Wand. Der Grundboden liegt überall bei 0, Böden liegen darauf. Auf einer Treppe zeigt Spots Nase immer bergauf: vorwärts hoch, rückwärts runter — nimmt ein Programm sie falsch herum, bleibt Spot an der Kante stehen und der Lauf sagt es (treppe_verweigert). In config.toml steht unter [limits] der Treppenmodus treppen = "auto"; mit "aus" sind Rampen und Treppen für Spot tabu, im Sim wie am Roboter.

2D | 3D. Der Knopf rechts über der Zeichnung (oder Tab) schaltet auf eine 3D-Sicht: linke Maustaste wählt, rechte dreht die Kamera, mittlere (oder Shift+rechts) schwenkt, das Rad zoomt, Home rahmt den Raum; die Tasten sind dieselben wie in 2D. Ohne OpenGL 3.3 bleibt der Knopf grau und sagt, warum (QT_OPENGL=software hilft oft); der Editor arbeitet dann in 2D weiter.

Aus einer Karte. „Rekonstruieren…" macht aus einer GraphNav-Karte (aus „Karten" oder ein beliebiger Ordner mit graph und waypoint_snapshots/) einen Raum: Wände aus der Punktwolke (Höhenband über dem Boden, 5-cm-Gitter, Sichtprüfung gegen Tiefen-Artefakte, Linien, Türen als Lücken, begradigt auf rechte Winkel), AprilTags aus den Ankern mit Blickrichtung und Höhe, Start am ersten Wegpunkt. Die Punktwolke bleibt als graues Pauspapier unter der Zeichnung — dort ziehst du nach, was die Automatik falsch hat, und speicherst den Raum wie jeden anderen (das Pauspapier liegt daneben als .pauspapier). Ohne Punktwolken wird der Pfad ein Schlauch. Der Katakomben-Gang der Schule (107 Wegpunkte, 31 Tags) braucht dafür rund sechs Sekunden.

Korrigieren. Eine rekonstruierte Karte hat Wände mit Lücken und einen Boden nur dort, wo Spot gelaufen ist. „Korrigieren…" listet jede Lücke (Lücke, Ecke, Anschluss, und Wände quer über den Weg) mit einem Vorschlag und dem Grund: lief der Roboter hindurch, ist es ein Durchgang; liegen Pauspapier-Punkte in der Lücke, ist es Wand; sonst „unklar" — du entscheidest. Die gewählte Zeile leuchtet in der 2D-Sicht auf. „Anwenden" schliesst die Wände und baut das Gelände: ein Höhenraster, das jeden Gang bis an die Wände füllt, mit dem Gefälle des gelaufenen Wegs und weichen Übergängen an Abzweigungen (Rampen und Podeste der Rekonstruktion gehen darin auf, Treppen bleiben und stehen darauf). Wo weder Wand noch Punkte aufhalten, endet der Boden 2 m neben dem Weg, und die offenen Ränder leuchten orange, bis du die Wand nachziehst und den Korrigierer nochmals laufen lässt. Das Gelände steht als eine Zeile in der Elementliste, ist nur gerechnet (nicht von Hand zu ziehen), erscheint in 2D als Relief mit Höhenlinien, in 3D als Fläche und in MuJoCo als Boden; spot.state.z folgt ihm. Alles zusammen ist ein Schritt für Ctrl+Z. Gespeichert wird es als .gelaende neben der Raumdatei; das Pauspapier trägt seit Fassung PAUS2 auch den gelaufenen Weg, damit der Korrigierer jederzeit neu rechnen kann.

Tasten wie in Blender, in beiden Sichten:

Taste Wirkung
G / R / S Bewegen / Drehen / Skalieren der Auswahl
X / Y / Z Achse sperren (Z: beim Bewegen heben, beim Skalieren die Höhe)
Ziffern, ., - Zahl eingeben; Enter bestätigt, Esc bricht ab
Shift+D / Entf duplizieren / löschen
Ctrl+Z / Ctrl+Y zurück / vor
A / Alt+A alles / nichts auswählen
Home alles zeigen — das Rad zoomt, die mittlere Taste schwenkt
Ctrl+S speichern
with spotlab.connect(backend="sim") as spot:      # oder raum="durchgang"
    spot.power_on()
    spot.stand()
    spot.move(forward=1.5)
    for tag in spot.tags():
        print(f"Tag {tag.id}: {tag.distance:.1f} m")

An Wänden bleibt Spot stehen — kein Fehler, kein Abbruch, so wie am echten Gerät. Die Stelle wird in der Zeichnung markiert und im Protokoll vermerkt.

Zuschauen, während es läuft

Über dem Editor steht „Wo läuft es?" mit bis zu vier Möglichkeiten:

Wahl Was passiert
Echter Spot der Roboter fährt
Trockenlauf (nur Text) nichts bewegt sich, das Programm läuft durch — keine Position
Übungsraum (virtuell) Spot fährt durch das gewählte Zimmer, als Zeichnung
Übungsraum 3D (MuJoCo) dasselbe im 3D-Zimmer mit Kameras, Tiefengitter und Kollision — nur, wenn spotsim installiert ist (unten)

Bei Übungsraum (virtuell) geht ein eigenes Fenster auf und zeichnet die Fahrt mit, während sie läuft — Weg, Blickrichtung, Anstösse, die letzte Ausgabezeile. Leg es neben den Editor, dann siehst du Code und Fahrt zugleich. Der Knopf „▶ Offene Datei starten" im Raumeditor stellt diese Wahl selbst ein, speichert vorher, wenn nötig, und kann den echten Spot nicht erreichen. Auch ein Start aus „Code" fährt im Raum, der im Raumeditor offen ist — ist der noch nicht gespeichert (neu, rekonstruiert oder geändert), fragt spotlab nach einem Namen; wer abbricht, startet nichts. Das Übungsfenster zeigt danach den Raum, den der Lauf wirklich bekommen hat: fährt ein Programm ohne Raum, bleibt die Zeichnung leer und sagt es. spot.tags() und spot.obstacles() arbeiten dabei aus der Raumgeometrie, du kannst also die ganze Bibliothek ohne Roboter üben.

Eigene Räume: eine TOML-Datei unter <arbeitsordner>/raeume/<name>.toml — der Raumeditor schreibt sie; von Hand geht es auch, gebaut wie die mitgelieferten unter welt/vorlagen/.

Beispiele. Im Arbeitsordner liegt immer das Projekt Beispiele — die GUI legt es beim Start an und ergänzt, was fehlt; deine Änderungen bleiben. durchgang_finden.py zeigt, wie Spot laufend — ohne anzuhalten, mit spot.walk(…, stop=False) — aus spot.obstacles().free_distance(…) die offenste Richtung wählt, durch die Tür in der Mitte in den Nachbarraum geht, den Tag dort als Beweis nimmt und sich setzt — im 2D-Raum, in 3D und am echten Spot dasselbe Programm.

Übungsraum 3D

backend="mujoco" ist derselbe Sim mit einem Körper: der Menagerie-Spot aus matura-spot, kinematisch gesetzt (Weg A — Wiedergabe statt Regelung, Spec docs/superpowers/specs/2026-09-06-uebungsraum-3d-design.md). Was dazukommt:

  • Kollision an der echten Geometrie statt an einem Kreis (welt/kollision.py).
  • spot.obstacles() aus fünf gerenderten Tiefenbildern, durch denselben Entpacker wie am Roboter — mit Verdeckung und unbekannten Zellen: was hinter dem Tisch liegt, ist nicht frei, weil ungesehen.
  • spot.tags() mit Kamerablickfeld, Ausrichtung und Sichtstrahl — ein Tag hinter dem Tisch bleibt unsichtbar.
  • spot.camera("frontleft_fisheye_image") und die fünf Tiefenkameras.
  • Das Übungsfenster zeigt das gerenderte Zimmer über der Zeichnung. Verfolgen schaltet auf eine Kamera, die schräg hinter Spot mitfährt und in seine Richtung schaut; das Mausrad über dem Bild zoomt in beiden Ansichten. Der Wunsch geht als kamera.json ins Lauf-Verzeichnis und bleibt über Läufe hinweg erhalten.

Selbst fahren. „🎮 Fahren" im Raumeditor startet fahren.py aus dem Projekt Beispiele im gewählten Raum (3D, wenn MuJoCo da ist, sonst 2D) und öffnet das Übungsfenster mit der Verfolgerkamera; solange gefahren wird, gehören alle Tasten diesem Fenster. Dort fährst du mit W/S vor und zurück, A/D seitwärts, Q/E drehen; die Leertaste hält, „Stopp" beendet. Die Tasten gehen als fahrt.json ins Lauf-Verzeichnis, und ein Befehl, der älter ist als eine halbe Sekunde, heisst Stopp — losgelassene Taste, eingeschlafene GUI. Es ist ein Lauf wie jeder andere: aufgezeichnet, mit den Tempogrenzen aus der Konfiguration, und danach als Video zu haben.

Gemessen sind Gangarten (12.08.2026) und die Antwort auf move() (02.09.2026: Anfahren, Reisetempo, Bremsen, Totzeiten); die Standhöhe folgt aus den gemessenen Winkeln über die Kinematik des Modells und liegt 2–8 mm neben der gemessenen. Annahmen stehen in bericht() und in lauf.json.

Video. Nach einem Lauf steht im Übungsfenster „🎬 Video speichern", in der Ansicht „Läufe" derselbe Knopf für jeden alten Lauf — auch für 2D- und für echte Läufe: das Video entsteht nachträglich aus zustand.jsonl (Pose und zwölf Gelenke, 10 Hz, auf 30 fps interpoliert), nicht aus dem Bildschirm. Auf der Kommandozeile: spotlab film <lauf> → <lauf>/film.mp4.

Installation (einmalig; matura-spot liegt neben spotlab):

pip install -e .[sim]
pip install -e ../matura-spot
python ../matura-spot/scripts/fetch_menagerie.py

Sperrzonen — Gefahr, die kein Sensor sieht

Glas löst kein Sensor des Spot: die Stereokameras schauen hindurch, das Hindernisgitter meldet freie Fläche. Dasselbe gilt für eine Treppe hinter der Ecke oder einen Bereich mit teurem Gerät. Wer es weiss, ist der Mensch.

Im Raumeditor gibt es dafür das Werkzeug Sperrzone: ein Rechteck aufziehen, Namen und Grund eintragen („Glasfront"). Der Roboter fährt nicht hinein — im Übungsraum bleibt er an der Grenze stehen wie an einer Wand, und am echten Gerät nimmt das Explorationsskript aus matura-spot die Vorwärtsfahrt aus dem Kommando (--raum <name>).

Eine Zone ist eine Regel, kein Hindernis: sie steht in keinem Hindernisgitter und taucht in keiner Rekonstruktion auf. Genau deshalb hält sie dort, wo der Sensor „frei" sagt.

Am echten Roboter braucht sie die Verortung: der Raum trägt seit Fassung 5 seinen Kartenbezug ([karte]), das Skript spielt die Karte ein und verortet sich; ohne das fährt es nicht vorwärts. Eine Zone an der falschen Stelle wäre schlimmer als keine.

Fahren — den echten Spot über die Tastatur

In der Ansicht Fahren startet „🎮 Fahrt beginnen" das Programm fahren.py aus dem Projekt Beispiele am echten Spot — über denselben Startweg wie „Starten" im Editor, also mit Lease, Not-Aus-Endpunkt, den Tempogrenzen aus der Konfiguration und der Aufzeichnung wie bei jedem Lauf. Sobald der Lauf steht, gehören die Tasten dem Tab: W/S vor und zurück, A/D seitwärts, Q/E drehen, Leertaste oder Esc hält. Losgelassen heisst Stopp — ein Fahrbefehl, der älter ist als eine halbe Sekunde, gilt nicht mehr, und jedes Kommando trägt eine Endzeit von rund einer Sekunde: stirbt die GUI oder der Lauf, steht der Roboter. Wechselst du den Reiter oder verliert das Fenster den Fokus (Alt-Tab mit gehaltenem W), lässt der Tab alle Tasten los und Spot steht.

Der Blick nach vorn. Während der Fahrt zeigt der Tab, was die beiden Frontkameras sehen — zu einem Bild zusammengesetzt wie auf dem Tablet, in Farbe, wo der Spot Farbe kann, und so schnell, wie er Bilder liefert (die erreichte Rate steht unter dem Bild). Im Übungsraum steht dort stattdessen das gerenderte Zimmer von aussen. Beides kommt als Datei aus dem Lauf-Verzeichnis; die Oberfläche spricht nie selbst mit dem Roboter.

Drei Tempostufen, als Auswahl im Tab oder mit den Tasten 1, 2, 3 während der Fahrt: Langsam 0.2 m/s (vorausgewählt), Normal 0.4 m/s, Schnell 0.8 m/s; seitwärts und drehen skalieren mit. Der Deckel aus config.toml gilt zusätzlich, „Schnell" ist genau die Voreinstellung max_speed, und die Drehrate wird dort auf max_turn_rate geklemmt. Ein Wechsel wirkt sofort, auch bei gehaltener Taste. Dieselben Stufen gelten im Übungsfenster (dort ist Normal vorausgewählt). „■ Stopp" ist derselbe freundliche Stopp wie in der Live-Ansicht, der NOT-AUS steht im Kopf, in jedem Reiter. Freifläche, Aufsicht, Tablet mit Not-Aus in Reichweite — und vor dem ersten Mal Abnahmepunkt A1 (docs/ABNAHME.md), sonst ist nicht belegt, dass der Not-Aus am Tablet während des Laufs wirkt.

Folgen — Spot geht dir hinterher

Halte ein AprilTag in der Hand oder häng es an den Rucksack, starte Beispiele/folgen.py und geh los. Spot dreht sich zu dir und hält Abstand.

Das Programm besteht aus zwei Teilen, die getrennt austauschbar sind. Ein Ziel-Finder sagt nur, wo das Ziel ist: Peilung in Grad, Abstand in Metern. Der Regler hält daraus Abstand und Kurs. Damit lassen sich Strategien vergleichen, statt sie zu behaupten:

folgen.folge(spot, folgen.tag_finder(), lauf_dir=spot.recorder.dir)   # AprilTag
folgen.folge(spot, folgen.personen_finder(), lauf_dir=spot.recorder.dir)  # Spots Tracker

Gesichter, und wo sie aufhören. gesicht_finder sucht mit YuNet aus OpenCV Gesichter im zusammengesetzten Kamerabild. Das braucht das Extra pip install "spotlab[gesicht]" und die Modelldatei face_detection_yunet_2023mar.onnx unter ~/.spotlab/modelle/ (oder SPOTLAB_GESICHTSMODELL). Sie liegt im OpenCV-Zoo — über git-lfs, der gewöhnliche raw-Link liefert also einen 131 Byte grossen Zeiger statt der 232 589 Bytes des Modells. Fehlt sie, fällt der Finder beim ersten Aufruf aus, das Tag trägt allein weiter, und der Grund steht genau einmal in runs/<lauf>/diagnose.log. Wer meint, der Erkenner erkenne ihn nicht, schaut zuerst dort nach.

Zwei Dinge sind daran gemessen und nicht behauptet:

Abstand höchster sichtbarer Punkt
1.5 m 1.20 m
2.0 m 1.45 m
3.0 m 1.94 m

Das Bild, das zum Erkenner geht, wird vorher aufgehellt — die Frontbilder sind im Gebäude dunkel (gemessen: mittlere Helligkeit 36 von 255), und YuNet blieb darauf mit 0.37 unter der Schwelle. Nach dem Histogrammausgleich findet es dasselbe Gesicht mit 0.71. Die Fahransicht bleibt unberührt. Ehrlich dazu: auch danach fand der Erkenner das Gesicht nur in 2 von 60 Takten — die Grenze ist die Geometrie, nicht die Helligkeit.

Spots Frontkameras schauen nach unten. Ein stehender Mensch hat deshalb erst ab gut zweieinhalb Metern ein Gesicht im Bild; näher sieht Spot Beine. Und ein Kasten mit hoher Punktzahl ist noch kein Gesicht: über eine Aufzeichnung vom 12.08.2026 fand der Erkenner vier Kästen, davon war der beste eine Stuhllehne und der zweitbeste ein Schienbein. Deshalb prüft spotlab jeden Kasten gegen die Geometrie nach — aus Höhenwinkel und gemessener Entfernung aus der Tiefenkamera folgt die Höhe über dem Boden, und was nicht auf Kopfhöhe liegt, fällt heraus. Die Entfernung wird nie aus der Kastengrösse geschätzt: an ihr hängt der Mindestabstand.

Weil die Gesichtssuche unterhalb von zweieinhalb Metern nichts findet, staffelt man sie:

folgen.folge(spot, folgen.zuerst(folgen.gesicht_finder(), folgen.tag_finder()), lauf_dir=…)

zuerst nimmt den ersten Finder, der etwas findet. Fällt einer aus, weil OpenCV oder das Modell fehlt, tragen die übrigen weiter, und der Grund steht im Protokoll.

personen_finder nutzt Spots eigenen Personen-Tracker über spot.people(). Kein eigenes Modell: der Tracker steckt in der Firmware und liefert Nummer, Position, Geschwindigkeit und eine Sicherheit. Ob dein Roboter ihn hat, zeigt erst das Gerät — findet er niemanden, bleibt Spot stehen.

Am Schul-Spot gemessen (11.09.2026): dieser Roboter verfolgt keine Menschen. 560 Abfragen über drei Läufe, kein einziges verfolgtes Objekt — und das ohne jeden Filter, während derselbe Dienst gleichzeitig AprilTags meldete. Der Finder ist trotzdem richtig und bleibt; er findet hier nur nichts. Die Zahlen stehen bei Abnahmepunkt A34 Teil 1.

Die Schranken gelten alle gleichzeitig, und jede, die ihre Daten nicht lesen kann, verbietet die Fahrt statt sie zu erlauben:

Schranke Wirkung
Mindestabstand Näher kommt Spot nie, und rückwärts fährt er gar nicht — nach hinten sieht er nichts.
Ziel verloren Er hält sofort, im selben Takt, nicht nach einer Frist.
Hindernisgitter Vorwärts nur, wenn voraus Platz gemeldet ist.
Kopfraum Vorwärts nur, wenn nichts über dem Weg hängt — das Gitter ist eine Bodenkarte und sieht keine Tischplatte.
Sperrzonen Nur mit Raum und Verortung; ohne beides sagt er es, statt Sicherheit vorzutäuschen.

Höher schauen während der Fahrt. Spots Frontkameras schauen nach unten, und deshalb kommt ein stehendes Gesicht erst spät ins Bild. Mit blick_grad hebt Spot beim Gehen die Nase, und die Kameras schauen mit:

Nickwinkel Gesicht 1.60 m sichtbar ab Boden erst ab
0° 2.31 m 0.32 m
10° 1.46 m 0.52 m
15° 1.18 m 0.66 m
folgen.folge(spot, finder, lauf_dir=…, blick_grad=12)

Zehn bis fünfzehn Grad reichen, damit ein Gesicht schon auf Folgeabstand im Bild ist. Der Preis steht in der rechten Spalte: der Boden dicht vor den Füssen fällt aus dem Blick, und das ist der Bereich, in dem die Hindernisschranke prüft. Deshalb ist die Vorgabe null. Die Neigung bleibt auch im Stehen, sonst legt Spot die Nase ab, sobald er im Wunschabstand ist, verliert das Gesicht und pendelt. Wie viel Neigung der Roboter im Laufen wirklich zulässt, misst Abnahmepunkt A34.

Der Tempodeckel aus config.toml gilt zusätzlich. Spot bewegt sich dabei autonom: Freifläche, Aufsicht, Tablet mit Not-Aus in Reichweite, und vor dem ersten Mal A1 und A34.

Gehzeit — Spot misst einen Schulversuch mit

Ein Versuch aus der Verhaltensbiologie: wie schnell gehen Leute, und hängt das von der Gruppengrösse ab? Mit der Stoppuhr in der Hand misst man immer nur eine Gruppe. Spot stellt sich seitlich in den Gang und stoppt für jeden gleichzeitig.

Aufbau: zwei AprilTags an die Enden der Strecke, Spot so hinstellen, dass er beide sieht, jede Person trägt ein Tag am Rucksack. Dann im Reiter Gehzeit auf „▶ Versuch starten" drücken — oder Beispiele/gehzeit.py aus dem Editor starten, wenn du am Programm etwas ändern willst.

Strecke 8.42 m zwischen Tag 1 und Tag 2 (Streuung 0.03 m, 12 Abtastungen).
  tag7: 6.10 s für 8.42 m = 1.38 m/s
  tag8: 6.35 s für 8.42 m = 1.33 m/s
  tag9: verworfen (gestoppt)

Die Strecke wird gemessen, nicht eingetippt. Ein eingetippter Wert stimmt beim ersten Aufbau und bleibt stehen, wenn jemand die Tags am nächsten Tag zwei Meter weiter hängt — die Zeiten sähen weiter richtig aus, und jedes Tempo wäre um denselben Faktor falsch. Gerechnet wird über den Median mehrerer Blicke, und wie stark sie streuten, steht daneben.

Spot bewegt sich dabei nicht und hält kein Lease (connect(nur_lesen=True)): die Aufsichtsperson behält das Tablet in der Hand.

Wer stehen bleibt, umkehrt oder verschwindet, wird verworfen — mit Grund in der Tabelle, nicht stillschweigend.

Die Gruppengrösse trägt ein Mensch nach. Ob drei Leute eine Gruppe waren oder drei Einzelne, die zufällig gleichzeitig losgingen, kann Spot nicht wissen. Er schlägt vor, wer gleichzeitig unterwegs war, und hält Bilder des Abschnitts bereit. Derselbe Reiter zeigt sie: links die Durchgänge, rechts die Bilder zum ausgewählten, darunter Klasse und Gruppengrösse. Wer lieber tippt:

python -m spotlab.experiment.nachtrag runs/20260910T094103Z_ab12cd34

So oder so stehen dort die Durchgänge mit Uhrzeit, Zeit und Tempo, dazu die Bilder. Klasse und Gruppengrösse kommen in dieselbe Tabelle — gehzeit/gehzeit.csv, Semikolon und Dezimalkomma, öffnet sich mit einem Doppelklick. Am Schluss fragt das Programm, ob die Bilder gelöscht werden sollen: es sind Aufnahmen von Mitschülern, gemacht für genau eine Frage.

Kein Alter aus dem Gesicht, und das ist Absicht: über die Aufzeichnung vom 12.08.2026 fand der Gesichtserkenner in 4 von 107 Takten etwas, und die zwei nachgesehenen Treffer waren eine Stuhllehne und ein Schienbein. Eine Altersschätzung darauf stünde ununterscheidbar neben gemessenen Zeiten.

Umwelt — was Spot gerade sieht

Der Spot führt selbst eine Liste der Objekte, die er erkennt: AprilTags, Dockingstationen, Türen. Dazu ein Hindernisgitter mit dem Abstand zum nächsten Hindernis je Zelle.

In der Ansicht Umwelt steht dafür ein Knopf: „Umgebung abfragen".

   apriltag 1     2.06 m   +14 Grad   vor 3 s
   dock 3         1.00 m     +0 Grad   vor 3 s

Die Abfrage hält kein Lease und sendet kein Kommando. Der Spot kann sich dadurch nicht bewegen, und sie funktioniert, während jemand anderes mit dem Tablet fährt. Genau dafür ist sie gedacht: nachsehen, ob Spot einen Tag überhaupt erkennt und aus welcher Entfernung — bevor ein Programm startet, das ihn bewegt.

Im eigenen Programm ist das connect(nur_lesen=True): die Sitzung meldet sich an und synchronisiert die Uhr, holt aber kein Lease und registriert keinen Not-Aus-Endpunkt. Sie kann den Spot nicht bewegen — nicht, weil das Programm es unterlässt, sondern weil die Fähigkeit fehlt.

Im eigenen Programm sind es die Verben spot.tags(), spot.world_objects() und spot.obstacles() (siehe unten).

Ein Programm

import spotlab

with spotlab.connect() as spot:
    spot.power_on()
    spot.stand()

    spot.move(forward=1.0)       # 1 m vorwärts, wartet bis angekommen
    spot.move(turn=90)           # 90 Grad drehen

    bild = spot.camera("frontleft")
    bild.save("vorne.png")

    print(spot.battery, "%")

connect() meldet an, synchronisiert die Uhr, registriert den Not-Aus und holt das Lease. Am Ende des with-Blocks setzt sich der Spot hin und schaltet die Motoren ab — auch wenn das Programm mit einem Fehler abbricht.

Die Motoren gehen beim Verbinden nicht an. spot.power_on() ist eine eigene Zeile, die jemand geschrieben haben muss: ein 30-kg-Roboter steht nicht auf, nur weil jemand ein Programm gestartet hat.

Verben

Bereich Verben
Leistung power_on(), power_off(), is_powered, battery
Haltung stand(height=0.0), sit()
Bewegung move(forward, left, turn), walk(vx, vy, wz, duration), stop()
Kameras cameras(), camera(name), state
Umwelt tags(id=None), world_objects(kinds=None), obstacles(), stairs()
Karten load_map(name), localize(), navigate_to(ziel), waypoints()
Messen messfenster(name, hz=50)
Roh robot, send(command), close()

move() ist eine relative Zieltrajektorie und wartet auf die echte Rückmeldung des Roboters — turn in Grad. walk() ist das Geschwindigkeitskommando des SDK und sendet intern automatisch nach, weil Geschwindigkeitskommandos beim echten Spot ablaufen.

spot.cameras() meldet, was das aktive Backend wirklich hat. Fehlt eine Fähigkeit, gibt es einen klaren Fehler statt einer Attrappe.

spot.stairs() nennt die Treppen in Sicht, nächste zuerst: direction („auf" — Spot steht am Fuss, „ab" — am Kopf), steps, rise_m, distance, bearing zur nächsten Kante und axis_bearing, die Richtung bergauf. spot.move(turn=treppe.axis_bearing) stellt die Nase bergauf, und so nimmt Spot jede Treppe: vorwärts hoch, rückwärts runter — ohne sich oben umzudrehen. Am Roboter kommen die Treppen aus der Firmware, im Übungsraum aus dem Raum; das Beispiel treppe_steigen.py zeigt den ganzen Weg.

Die Umwelt-Verben sprechen Grad und Meter, dieselbe Einheit wie move():

for tag in spot.tags():
    print(f"Tag {tag.id}: {tag.distance:.1f} m, {tag.bearing:+.0f} Grad")

spot.move(turn=spot.tags()[0].bearing)      # dreh dich zum nächsten Tag

Die Liste ist nach Distanz sortiert, spot.tags()[0] ist also ohne Nachdenken das nächste Ziel. Nichts gesehen ist eine Antwort, kein Fehler — dann kommt [] zurück. spot.obstacles() liefert das Hindernisgitter mit distance_at(x, y) und is_free(x, y); wo Spot nicht hingesehen hat, gilt „nicht frei", nicht „frei".

Kommandos

Kommando Zweck
spotlab login IP, Benutzer und Passwort hinterlegen
spotlab doctor stufenweise Diagnose, bricht beim ersten Fehler ab; sagt auch, was dieser Roboter kann (Lizenz, Nutzlasten, Dienste) und wann sein Zertifikat abläuft
spotlab new <name> Projekt mit lauffähiger Vorlage anlegen
spotlab open [projekt] in VS Code öffnen
spotlab run <datei> [--dryrun] starten und aufzeichnen
spotlab runs · runs <id> Läufe auflisten und ansehen
spotlab lease [--take] wer steuert den Spot; bewusste Übernahme
spotlab gui das Fenster öffnen (braucht pip install -e .[gui])
spotlab maps aufgezeichnete Karten auflisten
spotlab record-map <name> eine GraphNav-Karte aufzeichnen
spotlab mcp MCP-Server für Agenten, über stdin/stdout

Die Aufzeichnung hängt an connect(), nicht an spotlab run — wer in VS Code F5 drückt, bekommt sie genauso.

Läufe

Pro Lauf ein Verzeichnis unter runs/<zeitstempel>_<skript-hash>/:

Datei Inhalt
lauf.json Metadaten, Roboterkennung, Ergebnis, Dauer
ereignisse.jsonl Kommandos, Rückmeldungen, Fehler, mit Zeitstempel
zustand.jsonl 10 Hz: Pose, Geschwindigkeit, 12 Gelenke, Fusskontakte, Akku
bilder/ einzelne Kamerabilder aus spot.camera(...), mit Intrinsics
kamera/ nur im Beobachter-Modus: laufender Bildmitschnitt aller Kameras

kamera/ ist bewusst von bilder/ getrennt. Dort liegen die Schnappschüsse, die ein Schülerskript anfordert — eine Handvoll je Lauf, und die Live-Ansicht sucht darin bei jedem Takt das neueste Bild. Der Mitschnitt einer Messfahrt sind zehntausend Dateien; er bekommt deshalb ein eigenes Verzeichnis mit anhängendem Index kamera.jsonl. Bilder liegen dort so, wie der Roboter sie geschickt hat: JPEG als .jpg, Tiefe roh als .raw — umkodieren hiesse bei Tiefenbildern, die Millimeterwerte wegzuwerfen.

Sicherheit

Zwei getrennte Wege, beide korrekt für ihre Situation:

  • Geordnetes Ende (durchgelaufen, Ausnahme, Ctrl-C): stoppen → sicher hinsetzen → Motoren aus → Lease frei → Not-Aus-Endpunkt abmelden.
  • Prozess hart getötet: die Keepalives sterben, der Roboter geht von selbst in den sicheren Zustand. Dieser Weg lässt sich nicht kaputtprogrammieren.

spotlab registriert seinen Not-Aus-Endpunkt zusätzlich zur bestehenden Konfiguration, damit der physische Not-Aus am Tablet wirksam bleibt. Der bequeme SDK-Weg force_simple_setup() würde ihn verdrängen und wird deshalb nicht benutzt.

Vor dem ersten Schülerbetrieb ist die Abnahmeliste in docs/ABNAHME.md abzuarbeiten, insbesondere Punkt A1 (Not-Aus-Koexistenz am echten Gerät).

Für die Lehrperson: das Tempo herunterdrehen

In der Ansicht Spot lassen sich max_speed und max_turn_rate setzen; sie landen in ~/.spotlab/config.toml. Für eine Anfängerstunde lohnt sich 0.2 m/s statt der Voreinstellung 0.6. Der Deckel wirkt auf allen drei Wegen — walk(), move() und die autonome Fahrt — und ein Schülerskript kann ihn nicht überschreiben.

Werte, die keine Sicherheitsgrenze mehr wären, werden abgewiesen statt stillschweigend übernommen: inf, 0 und negative Zahlen führen zu einer Fehlermeldung beim Start, nicht zu einem wirkungslosen Deckel.

Weiterlesen

Für die Benutzung

Für die Entwicklung — jede Stufe hat ihren eigenen Entwurf mit Begründungen:

Stufe Entwurf
Fundament 2026-08-06-spotlab-fundament-design.md
Oberfläche 2026-08-07-spotlab-gui-design.md
GraphNav 2026-08-07-spotlab-graphnav-design.md
Editor 2026-08-07-spotlab-ide-design.md
Anbindung + MCP 2026-08-07-spotlab-anbindung-design.md
Kalibrierung 2026-08-08-spotlab-kalibrierung-design.md
Beobachter-Modus 2026-08-09-spotlab-beobachtung-design.md
Umwelt 2026-09-02-spotlab-umwelt-design.md
Übungsraum 2026-09-02-spotlab-uebungsraum-design.md

Tests

pytest

Alle Tests laufen ohne Roboter und ohne Netz. Das dryrun-Backend ist das Standard-Testdouble; die Kommando-Protobufs werden dabei echt gebaut und gegen die SDK-Schemata geprüft.

Quantitative Wahrnehmung aus Python

with connect() as spot:
    if spot.supports("depth"):
        depth = spot.depth("frontleft")
        print(depth.distance_at(212, 120))  # Meter oder None
        cloud = depth.point_cloud(frame="body", stride=2)
        cloud.save("punkte.ply")
    if spot.supports("local_grid"):
        print(spot.grid_types())

Tiefenbilder, Punktwolken und LocalGrids: Rohdatenexport, Gültigkeitsmasken und Koordinatenrahmen. Keine Motorfreigabe nötig; connect() verwendet weiterhin den normalen Sitzungskern.

Experimenteller Physikmodus

Neu in der Backend-Auswahl: Physik 3D (experimentell, ebener Boden). Der Koerper wird von Kontaktkraeften getragen. Start im Stand; walk/stop, Sensorik und Livebild sind angebunden. Treppen, sit und move sind noch nicht unterstuetzt. Anleitung und Grenzen.

Experimenteller Einzelstufenversuch: physik_einzelstufe.py mit Raum physik_einzelstufe und Start (0, 0, 0) im Modus Physik 3D. 6-cm-Podest vorwärts hinauf, rückwärts herunter; mehrere Minuten Laufzeit. Grenzen und Messwerte: docs/PHYSICS.md.

Neu: physik_treppe_3stufen.py mit Raum physik_treppe_3stufen, Physik 3D und Start (0,0,0): drei niedrige 4-cm-Stufen vorwärts hinauf und rückwärts herunter. Etwa 7–12 Minuten; Grenzen und Messwerte in docs/PHYSICS.md.

Lizenz

Spotlab steht unter der MIT-Lizenz. Ausgenommen sind die unverändert übernommenen Dateien aus dem OpenCV Model Zoo in src/spotlab/backends/real/zoo/ (Apache 2.0, Lizenz im selben Ordner). Die Erkennermodelle im Schüler-ZIP stehen unter MIT beziehungsweise Apache 2.0; siehe tools/modelle_lizenzen.txt.

About

Spotlab – eine Programmierumgebung für den Boston Dynamics Spot mit integriertem Code-Editor, 3D-Simulation und Steuerung des echten Roboters.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages