Dobot Magician – Python-Hilfsfunktionen

Dokumentation der aktuellen dobot.py für Thonny, die 64-Bit-Dobot-DLL und das Lochraster-Koordinatensystem.

Dokumentation Version 1.0.1korrigiertes Syntaxhighlighting
Basis: dobot.py Version 1.0.0Stand 15.07.2026
Kanonische DateiDobot-Python-Hilfsfunktionen.html
40 Spalten27 Zeilen16-mm-Lochrasterbeliebige ReferenzlöcherMOVJ und MOVLAlarm löschenVersionsabfrageOffline-Syntaxhighlighting

Version und Dokumentstand

Aktueller Stand: Diese Dokumentation ist auf dobot.py Version 1.0.0 vom 15.07.2026 abgestimmt.
KomponenteVersion / Stand
DokumentationVersion 1.0.1
dobot.pyVersion 1.0.0 · 15.07.2026
Versionsabfragedobot.version()
DateinameDobot-Python-Hilfsfunktionen.html
print(dobot.version())

Grundlage und Ordnerstruktur

Die Datei dobot.py bildet eine eigene, leicht lesbare Programmierschicht über DobotDllType.py und DobotDll.dll.

Eigenes Projekt: start.py ↓ Eigene Hilfsfunktionen: dobot.py ↓ Python-API: sdk64/DobotDllType.py ↓ 64-Bit-Bibliothek: sdk64/DobotDll.dll ↓ Dobot Magician
Dobot_Python\
│
├── dobot.py
│
├── sdk64\
│   ├── __init__.py
│   ├── DobotDllType.py
│   └── DobotDll.dll
│
└── projekt01\
    └── start.py

Empfohlener Programmstart

Die folgende Struktur berücksichtigt die Versionsabfrage, die korrigierte Alarmfunktion und die notwendige Queue-Reihenfolge für HOME- und Bewegungsbefehle.

from pathlib import Path
import sys

HAUPTORDNER = Path(__file__).resolve().parent.parent
if str(HAUPTORDNER) not in sys.path:
    sys.path.insert(0, str(HAUPTORDNER))

import dobot

print(dobot.version())

api = dobot.init("COM10")
dobot.alarme_loeschen(api)
dobot.queue_starten(api)

try:
    dobot.home(api)
    dobot.position_anzeigen(api)

    # Optional: Lochrasterplatte kalibrieren
    # dobot.plattenkalibrierung_setzen(...)
finally:
    dobot.queue_stoppen(api)
Queue-Reihenfolge: home() und fahre_zu() stellen Befehle mit isQueued=1 ein. Deshalb muss queue_starten(api) vor diesen Befehlen ausgeführt werden.

Konstanten

VERSION = "1.0.0"
VERSIONSDATUM = "15.07.2026"
PLATTE_SPALTEN = 40
PLATTE_ZEILEN = 27
PLATTE_RASTER_MM = 16.0

PLATTE_RASTER_MM dokumentiert das konstruktive Raster. Die tatsächliche XY-Umrechnung nutzt die aus drei Referenzlöchern berechneten Vektoren.

version() – Versionsabfrage

Gibt die eindeutige Versionsbezeichnung der geladenen dobot.py zurück.

Aktuelle Definition in dobot.py Version 1.0.0

def version():
    """Gibt die Versionsbezeichnung dieser ``dobot.py`` zurück.

    Beispiel:
        ``print(dobot.version())``
    """

    return f"dobot.py Version {VERSION} - Stand {VERSIONSDATUM}"
print(dobot.version())

Ausgabe: dobot.py Version 1.0.0 - Stand 15.07.2026

com_ports_ermitteln() – COM-Ports ermitteln

Ermittelt die vom Betriebssystem erkannten seriellen Schnittstellen. Wenn PySerial installiert ist, werden ausführliche Portbeschreibungen verwendet; unter Windows gibt es zusätzlich einen Registry-Fallback.

Aktuelle Definition in dobot.py Version 1.0.0

def com_ports_ermitteln():
    """Gibt die vom Betriebssystem erkannten seriellen Schnittstellen zurück.

    Das Ergebnis ist eine Liste aus Paaren:
    ``[(Portname, Beschreibung), ...]``.

    Wenn PySerial installiert ist, werden dessen ausführliche Angaben
    verwendet. Unter Windows dient andernfalls die Registrierungsdatenbank
    als Fallback.
    """

    try:
        from serial.tools import list_ports
    except ImportError:
        list_ports = None

    if list_ports is not None:
        ports = [
            (port.device, port.description or "Keine Beschreibung")
            for port in list_ports.comports()
        ]
        return sorted(ports, key=lambda eintrag: eintrag[0].upper())

    # Fallback ohne PySerial für Windows.
    if os.name == "nt":
        try:
            import winreg

            schluessel = winreg.OpenKey(
                winreg.HKEY_LOCAL_MACHINE,
                r"HARDWARE\DEVICEMAP\SERIALCOMM",
            )

            ports = []
            index = 0

            while True:
                try:
                    _, portname, _ = winreg.EnumValue(schluessel, index)
                    ports.append((portname, "Serielle Schnittstelle"))
                    index += 1
                except OSError:
                    break

            winreg.CloseKey(schluessel)
            return sorted(
                set(ports),
                key=lambda eintrag: eintrag[0].upper(),
            )

        except OSError:
            return []

    return []
ports = dobot.com_ports_ermitteln()
for port, beschreibung in ports:
    print(port, beschreibung)

comport_pruefen() – COM-Port prüfen

Prüft, ob der gewünschte COM-Port vorhanden ist. Bei einem Fehler werden die gefundenen Ports ausgegeben und das Programm beendet.

Aktuelle Definition in dobot.py Version 1.0.0

def comport_pruefen(comport):
    """Prüft, ob der gewünschte COM-Port vom Betriebssystem erkannt wird.

    Ist der Port nicht vorhanden, werden alle erkannten seriellen
    Schnittstellen ausgegeben und das Programm mit Fehlercode 1 beendet.
    """

    ports = com_ports_ermitteln()
    vorhandene_portnamen = {
        portname.upper()
        for portname, _ in ports
    }

    if comport.upper() in vorhandene_portnamen:
        print(f"Serielle Schnittstelle: {comport} ist vorhanden.")
        return

    print()
    print(f"FEHLER: Die serielle Schnittstelle {comport} ist nicht verfügbar.")
    print()

    if ports:
        print("Vom Betriebssystem erkannte COM-Ports:")

        for portname, beschreibung in ports:
            print(f"  {portname:<8} {beschreibung}")
    else:
        print("Es wurden keine seriellen Schnittstellen gefunden.")

    print()
    print("Das Programm wird beendet.")
    sys.exit(1)
dobot.comport_pruefen("COM10")

init() – Dobot initialisieren

Prüft den COM-Port, lädt die 64-Bit-DLL, verbindet den Dobot, setzt optional den Gerätenamen und gibt das API-Objekt zurück.

Aktuelle Definition in dobot.py Version 1.0.0

def init(
    comport="COM10",
    device_name=(
        "Dobot Magician - AG Young Engineers - "
        "Martin-Rinckart-Gymnasium"
    ),
):
    """Lädt die 64-Bit-DLL, verbindet den Dobot und gibt ``api`` zurück."""

    global _dll_suchpfad

    hauptverzeichnis = Path(__file__).resolve().parent
    sdk_verzeichnis = hauptverzeichnis / "sdk64"
    dll_datei = sdk_verzeichnis / "DobotDll.dll"

    print("Python-Version:    ", platform.python_version())
    print("Python-Architektur:", platform.architecture()[0])
    print("SDK-Verzeichnis:   ", sdk_verzeichnis)
    print("DLL-Datei:         ", dll_datei)
    print("DLL vorhanden:     ", dll_datei.exists())

    # Vor dem Laden der DLL prüfen, ob der gewünschte COM-Port existiert.
    comport_pruefen(comport)

    if not dll_datei.exists():
        raise FileNotFoundError(
            "Die Dobot-DLL wurde nicht gefunden:\n"
            f"{dll_datei}\n\n"
            "Erwartete Ordnerstruktur:\n"
            "Dobot_Python\\\n"
            "├── dobot.py\n"
            "├── sdk64\\\n"
            "│   ├── DobotDll.dll\n"
            "│   └── DobotDllType.py\n"
            "└── projekt01\\\n"
            "    └── start.py"
        )

    # Unter Windows können sich in sdk64 weitere benötigte DLLs befinden.
    if hasattr(os, "add_dll_directory"):
        _dll_suchpfad = os.add_dll_directory(str(sdk_verzeichnis))

    # DobotDll.dll über ihren vollständigen Pfad laden.
    api = ctypes.CDLL(str(dll_datei))

    result = dType.ConnectDobot(api, comport, 115200)
    print("ConnectDobot-Ergebnis:", result)

    # 0 bedeutet bei der Dobot-API: Verbindung erfolgreich.
    if result[0] != 0:
        meldungen = {
            1: "Dobot wurde nicht gefunden.",
            2: "Der COM-Port ist bereits belegt.",
        }
        meldung = meldungen.get(result[0], "Unbekannter Verbindungsfehler.")
        raise ConnectionError(
            f"Verbindung über {comport} fehlgeschlagen: {meldung} "
            f"(Fehlercode {result[0]})"
        )

    if device_name:
        dType.SetDeviceName(api, device_name)

    name = dType.GetDeviceName(api)
    seriennummer = dType.GetDeviceSN(api)

    print("Gerätename:   ", name)
    print("Seriennummer: ", seriennummer)

    return api
api = dobot.init("COM10")

warten_bis_fertig() – Auf Queue-Befehl warten

Wartet, bis der Dobot den angegebenen Queue-Index erreicht hat. Diese Funktion wird intern von Bewegungs- und HOME-Befehlen genutzt.

Aktuelle Definition in dobot.py Version 1.0.0

def warten_bis_fertig(api, ziel_index):
    """Wartet, bis ein Queue-Befehl vollständig ausgeführt wurde."""

    while dType.GetQueuedCmdCurrentIndex(api)[0] < ziel_index:
        dType.dSleep(100)
Wichtig: Die Queue muss laufen. Sonst kann die Funktion dauerhaft warten.

queue_starten() – Queue starten

Stoppt eine eventuell laufende Queue, löscht alte Befehle und startet die Befehlswarteschlange neu.

Aktuelle Definition in dobot.py Version 1.0.0

def queue_starten(api):
    """Stoppt eine laufende Queue, löscht sie und startet sie neu."""

    dType.SetQueuedCmdStopExec(api)
    dType.SetQueuedCmdClear(api)
    dType.SetQueuedCmdStartExec(api)
dobot.queue_starten(api)

queue_stoppen() – Queue stoppen

Stoppt die Ausführung der Dobot-Befehlswarteschlange.

Aktuelle Definition in dobot.py Version 1.0.0

def queue_stoppen(api):
    """Stoppt die Ausführung der Queue."""

    dType.SetQueuedCmdStopExec(api)
dobot.queue_stoppen(api)

position_lesen() – Position lesen

Liest die aktuelle kartesische Position und gibt (x, y, z, r) zurück.

Aktuelle Definition in dobot.py Version 1.0.0

def position_lesen(api):
    """Liest die kartesische Position X, Y, Z und R."""

    return dType.GetPose(api)[:4]
x, y, z, r = dobot.position_lesen(api)

position_anzeigen() – Position anzeigen

Liest die aktuelle Position und gibt X, Y, Z und R formatiert aus.

Aktuelle Definition in dobot.py Version 1.0.0

def position_anzeigen(api):
    """Liest und zeigt die aktuelle kartesische Position an."""

    x, y, z, r = position_lesen(api)

    print(
        f"X={x:.1f} mm, "
        f"Y={y:.1f} mm, "
        f"Z={z:.1f} mm, "
        f"R={r:.1f}°"
    )
dobot.position_anzeigen(api)

fahre_zu() – Zu einer absoluten Position fahren

Fährt zu einer kartesischen Zielposition und wartet auf das Bewegungsende. Ohne Angabe wird PTPMOVJXYZMode verwendet.

Aktuelle Definition in dobot.py Version 1.0.0

def fahre_zu(api, x, y, z, r, modus=None):
    """Fährt zu einer Zielposition und wartet auf das Bewegungsende."""

    if modus is None:
        modus = dType.PTPMode.PTPMOVJXYZMode

    ziel_index = dType.SetPTPCmd(
        api,
        modus,
        x,
        y,
        z,
        r,
        isQueued=1,
    )[0]

    warten_bis_fertig(api, ziel_index)
dobot.fahre_zu(api, 200.0, 100.0, 50.0, 0.0)
Sicherheit: Zielpunkt und Fahrweg müssen kollisionsfrei und innerhalb des Arbeitsbereichs liegen.

plattenkalibrierung_setzen() – Lochrasterplatte kalibrieren

Kalibriert die 40 × 27-Lochrasterplatte mit drei beliebigen erreichbaren Referenzlöchern. Die drei Rasterpositionen dürfen nicht auf einer gemeinsamen Geraden liegen.

Aktuelle Definition in dobot.py Version 1.0.0

def plattenkalibrierung_setzen(
    referenzloch_1,
    referenzloch_2,
    referenzloch_3,
    platten_z,
    standard_r=0.0,
):
    """Kalibriert die Platte mit drei beliebigen erreichbaren Löchern.

    Neue Schreibweise der Referenzpunkte:
        ``(spalte, zeile, x, y)``

    Beispiel:
        ``referenzloch_1=(2, 1, -73.8, -311.1)``

    Die drei Rasterpositionen dürfen nicht auf einer gemeinsamen Geraden
    liegen. Günstig sind zwei weit auseinanderliegende Löcher einer Zeile
    und ein drittes Loch in einer möglichst weit entfernten Zeile.

    Aus Gründen der Abwärtskompatibilität wird auch die bisherige
    Eckpunkt-Schreibweise mit drei XY-Tupeln unterstützt:

        ``loch_1_1=(x, y)``
        ``loch_40_1=(x, y)``
        ``loch_1_27=(x, y)``
    """

    global _plattenkalibrierung

    # Alte Schreibweise erkennen:
    # (x, y), (x, y), (x, y)
    alte_schreibweise = all(
        hasattr(ref, "__len__") and len(ref) == 2
        for ref in (
            referenzloch_1,
            referenzloch_2,
            referenzloch_3,
        )
    )

    if alte_schreibweise:
        referenzloch_1 = (
            1,
            1,
            referenzloch_1[0],
            referenzloch_1[1],
        )
        referenzloch_2 = (
            PLATTE_SPALTEN,
            1,
            referenzloch_2[0],
            referenzloch_2[1],
        )
        referenzloch_3 = (
            1,
            PLATTE_ZEILEN,
            referenzloch_3[0],
            referenzloch_3[1],
        )

    a = _referenzloch_lesen(referenzloch_1, "referenzloch_1")
    b = _referenzloch_lesen(referenzloch_2, "referenzloch_2")
    c = _referenzloch_lesen(referenzloch_3, "referenzloch_3")

    spalte_a, zeile_a, x_a, y_a = a
    spalte_b, zeile_b, x_b, y_b = b
    spalte_c, zeile_c, x_c, y_c = c

    ds_b = spalte_b - spalte_a
    dz_b = zeile_b - zeile_a
    ds_c = spalte_c - spalte_a
    dz_c = zeile_c - zeile_a

    determinante = ds_b * dz_c - ds_c * dz_b

    if abs(determinante) < 1e-9:
        raise ValueError(
            "Die drei Referenzlöcher sind für eine Kalibrierung "
            "ungeeignet: Ihre Rasterpositionen liegen auf einer Geraden."
        )

    dx_b = x_b - x_a
    dx_c = x_c - x_a
    dy_b = y_b - y_a
    dy_c = y_c - y_a

    # Dobot-Vektor für einen Schritt in Spaltenrichtung.
    sx = (dx_b * dz_c - dx_c * dz_b) / determinante
    sy = (dy_b * dz_c - dy_c * dz_b) / determinante

    # Dobot-Vektor für einen Schritt in Zeilenrichtung.
    zx = (ds_b * dx_c - ds_c * dx_b) / determinante
    zy = (ds_b * dy_c - ds_c * dy_b) / determinante

    # Aus dem erreichbaren Referenzloch A wird rechnerisch die Position
    # des möglicherweise nicht erreichbaren Lochs (1, 1) bestimmt.
    x_1_1 = (
        x_a
        - (spalte_a - 1.0) * sx
        - (zeile_a - 1.0) * zx
    )
    y_1_1 = (
        y_a
        - (spalte_a - 1.0) * sy
        - (zeile_a - 1.0) * zy
    )

    _plattenkalibrierung = {
        "loch_1_1": (x_1_1, y_1_1),
        "spaltenvektor": (sx, sy),
        "zeilenvektor": (zx, zy),
        "referenzloecher": (a, b, c),
        "platten_z": float(platten_z),
        "standard_r": float(standard_r),
    }
dobot.plattenkalibrierung_setzen(
    referenzloch_1=(2, 1, -73.8, -311.1),
    referenzloch_2=(39, 1, X_39_1, Y_39_1),
    referenzloch_3=(2, 26, X_2_26, Y_2_26),
    platten_z=-35.0,
    standard_r=0.0,
)

plattenkalibrierung_anzeigen() – Kalibrierung anzeigen

Zeigt die Referenzlöcher, das rechnerische Loch (1, 1), Platten-Z, Standard-R sowie die beiden Rastervektoren an.

Aktuelle Definition in dobot.py Version 1.0.0

def plattenkalibrierung_anzeigen():
    """Zeigt Referenzlöcher, Ursprung und Rastervektoren an."""

    k = _plattenkalibrierung_holen()
    spaltenvektor, zeilenvektor = _platten_rastervektoren()

    print("Kalibrierung der Lochrasterplatte")
    print("---------------------------------")

    for nummer, referenz in enumerate(k["referenzloecher"], start=1):
        spalte, zeile, x, y = referenz
        print(
            f"Referenz {nummer}: "
            f"Loch ({spalte:g}, {zeile:g}) -> "
            f"X={x:.2f}, Y={y:.2f}"
        )

    x11, y11 = k["loch_1_1"]

    print()
    print(
        "Rechnerisches Loch (1, 1): "
        f"X={x11:.2f}, Y={y11:.2f}"
    )
    print(f"Platten-Z:   {k['platten_z']:.2f} mm")
    print(f"Standard-R:  {k['standard_r']:.2f}°")
    print()
    print(
        "Rastervektor Spalte: "
        f"dx={spaltenvektor[0]:.4f}, "
        f"dy={spaltenvektor[1]:.4f}"
    )
    print(
        "Rastervektor Zeile:  "
        f"dx={zeilenvektor[0]:.4f}, "
        f"dy={zeilenvektor[1]:.4f}"
    )
dobot.plattenkalibrierung_anzeigen()

fahre_zu_loch() – Gelenkoptimiert zu einer Lochposition fahren

Rechnet Lochrasterkoordinaten in Dobot-Koordinaten um und fährt mit PTPMOVJXYZMode zur Zielposition.

Aktuelle Definition in dobot.py Version 1.0.0

def fahre_zu_loch(
    api,
    spalte,
    zeile,
    hoehe=30.0,
    r=None,
):
    """Fährt gelenkoptimiert zu einer Position im Lochraster.

    ``spalte`` und ``zeile`` beginnen bei 1.

    ``hoehe`` ist die Höhe in Millimetern über der Plattenoberfläche.
    Der sichere Standardwert beträgt 30 mm.

    Beispiel:
        ``fahre_zu_loch(api, 10, 8, hoehe=30)``
    """

    x, y, z, rotation = _loch_zu_dobot(
        spalte=spalte,
        zeile=zeile,
        hoehe=hoehe,
        r=r,
    )

    fahre_zu(
        api,
        x,
        y,
        z,
        rotation,
        dType.PTPMode.PTPMOVJXYZMode,
    )

    return x, y, z, rotation
dobot.fahre_zu_loch(api, 10, 8, hoehe=30)

fahre_zu_loch_linear() – Linear zu einer Lochposition fahren

Rechnet Lochrasterkoordinaten in Dobot-Koordinaten um und fährt den gesamten Weg linear mit PTPMOVLXYZMode.

Aktuelle Definition in dobot.py Version 1.0.0

def fahre_zu_loch_linear(
    api,
    spalte,
    zeile,
    hoehe=30.0,
    r=None,
):
    """Fährt auf einer geraden Bahn zu einer Position im Lochraster.

    Der gesamte Weg wird linear ausgeführt. Vor allem bei niedriger Höhe
    muss deshalb geprüft werden, ob der Fahrweg frei ist.

    Beispiel:
        ``fahre_zu_loch_linear(api, 10, 8, hoehe=5)``
    """

    x, y, z, rotation = _loch_zu_dobot(
        spalte=spalte,
        zeile=zeile,
        hoehe=hoehe,
        r=r,
    )

    fahre_zu(
        api,
        x,
        y,
        z,
        rotation,
        dType.PTPMode.PTPMOVLXYZMode,
    )

    return x, y, z, rotation
dobot.fahre_zu_loch_linear(api, 10, 8, hoehe=5)
Achtung: Bei niedriger Höhe muss der komplette lineare Fahrweg frei sein.

aktuelle_plattenposition() – Aktuelle Plattenposition bestimmen

Rechnet die aktuelle Dobot-Position zurück in (Spalte, Zeile, Höhe). Spalte und Zeile können Dezimalwerte enthalten.

Aktuelle Definition in dobot.py Version 1.0.0

def aktuelle_plattenposition(api, anzeigen=True):
    """Liefert die aktuelle Position im Lochraster-Koordinatensystem.

    Rückgabe:
        ``(spalte, zeile, hoehe)``

    Die Werte für Spalte und Zeile können Dezimalstellen enthalten.
    Ein ganzzahliger Wert bezeichnet die Mitte eines Lochs.

    Mit ``anzeigen=False`` wird die Ausgabe unterdrückt.
    """

    x, y, z, _r = position_lesen(api)
    spalte, zeile, hoehe = _dobot_zu_platte(x, y, z)

    if anzeigen:
        naechste_spalte = round(spalte)
        naechste_zeile = round(zeile)

        print("Aktuelle Plattenposition:")
        print(f"  Spalte: {spalte:.3f}")
        print(f"  Zeile:  {zeile:.3f}")
        print(f"  Höhe:   {hoehe:.2f} mm")
        print(
            "  Nächstes Loch: "
            f"({naechste_spalte}, {naechste_zeile})"
        )

    return spalte, zeile, hoehe
spalte, zeile, hoehe = dobot.aktuelle_plattenposition(api)

home() – HOME-Fahrt

Startet eine HOME-Fahrt und wartet auf deren Abschluss.

Aktuelle Definition in dobot.py Version 1.0.0

def home(api):
    """Führt eine HOME-Fahrt aus und wartet auf deren Abschluss."""

    print("HOME-Fahrt wird gestartet.")

    ziel_index = dType.SetHOMECmd(
        api,
        0,
        isQueued=1,
    )[0]

    warten_bis_fertig(api, ziel_index)

    print("HOME-Fahrt abgeschlossen.")
dobot.queue_starten(api)
dobot.home(api)
Wichtig: home() verwendet einen Queue-Befehl. Vorher muss die Queue gestartet worden sein.

test_z() – Interaktiver Z-Test

Erlaubt, ausgehend von der aktuellen Position neue Z-Koordinaten einzugeben. Die Bewegung erfolgt linear.

Aktuelle Definition in dobot.py Version 1.0.0

def test_z(api):
    """Erlaubt das interaktive Verändern der aktuellen Z-Koordinate."""

    print()
    print("Interaktiver Test der Z-Koordinate")
    print("----------------------------------")

    while True:
        x, y, z, r = position_lesen(api)

        print(
            f"Position: X={x:.1f}, Y={y:.1f}, "
            f"Z={z:.1f}, R={r:.1f}"
        )

        eingabe = input(
            "Neue Z-Koordinate oder 'a' zum Abbrechen: "
        ).strip()

        if eingabe.lower() == "a":
            break

        # Auch eine Eingabe mit deutschem Dezimalkomma wird akzeptiert.
        try:
            neue_z = float(eingabe.replace(",", "."))
        except ValueError:
            print("Ungültige Eingabe. Bitte eine Zahl oder 'a' eingeben.")
            continue

        fahre_zu(
            api,
            x,
            y,
            neue_z,
            r,
            dType.PTPMode.PTPMOVLXYZMode,
        )

        print("Erreichte Position:")
        position_anzeigen(api)

    print("Z-Test beendet.")
dobot.test_z(api)

ausfuehren() – Beispielablauf starten

Startet die Queue, zeigt die aktuelle Position an, führt test_z() aus und stoppt die Queue sicher in einem finally-Block.

Aktuelle Definition in dobot.py Version 1.0.0

def ausfuehren(api):
    """Startet den auf der Webseite beschriebenen Beispielablauf."""

    print()
    print("Roboterprogramm wird gestartet.")
    print("--------------------------------")

    queue_starten(api)

    try:
        print("Aktuelle Position:")
        position_anzeigen(api)

        test_z(api)
    finally:
        queue_stoppen(api)

        print()
        print("Roboterprogramm beendet.")
dobot.ausfuehren(api)

alarme_loeschen() – Alarmzustände löschen

Löscht die gespeicherten Alarmzustände des Dobot. Besteht die Ursache fort, kann der Alarm sofort erneut auftreten.

Aktuelle Definition in dobot.py Version 1.0.0

def alarme_loeschen(api):
    """Löscht alle gespeicherten Alarmzustände des Dobot.

    Achtung:
    Besteht die Ursache weiterhin, wird der Alarm erneut ausgelöst.
    """

    if api is None:
        raise RuntimeError("Der Dobot ist nicht verbunden.")

    dType.ClearAllAlarmsState(api)
dobot.alarme_loeschen(api)
Kein Ersatz für Fehlerbehebung: Vor dem Löschen muss die Ursache eines Alarms geklärt und beseitigt werden.

main() – Direktstart-Hinweis

Gibt einen Hinweis aus, wenn dobot.py direkt gestartet wird. Normalerweise wird eine Projektdatei wie start.py ausgeführt.

Aktuelle Definition in dobot.py Version 1.0.0

def main():
    """Hinweis beim direkten Start dieser Bibliotheksdatei."""

    print(
        "Diese Datei wird normalerweise nicht direkt gestartet.\n"
        "Bitte 'start.py' ausführen."
    )

Interne Funktionen

Diese Funktionen sind für die interne Umsetzung vorgesehen. Sie werden normalerweise nicht direkt aus einem Projektprogramm aufgerufen.

_referenzloch_lesen() – Prüft und zerlegt ein Referenzloch im Format (Spalte, Zeile, X, Y).
def _referenzloch_lesen(referenz, bezeichnung):
    """Prüft und zerlegt ein Referenzloch.

    Neue Schreibweise:
        ``(spalte, zeile, x, y)``

    Für die bisherige Eckpunktkalibrierung wird zusätzlich die alte
    Schreibweise ``(x, y)`` unterstützt.
    """

    try:
        werte = tuple(referenz)
    except TypeError as exc:
        raise TypeError(
            f"{bezeichnung} muss ein Tupel oder eine Liste sein."
        ) from exc

    if len(werte) != 4:
        raise ValueError(
            f"{bezeichnung} muss als "
            "(spalte, zeile, x, y) angegeben werden."
        )

    spalte, zeile, x, y = map(float, werte)
    _plattenkoordinate_pruefen(spalte, zeile)

    return spalte, zeile, x, y
_plattenkalibrierung_holen() – Liefert die gespeicherte Kalibrierung oder erzeugt eine verständliche Fehlermeldung.
def _plattenkalibrierung_holen():
    """Liefert die Kalibrierung oder erzeugt eine verständliche Meldung."""

    if _plattenkalibrierung is None:
        raise RuntimeError(
            "Die Lochrasterplatte ist noch nicht kalibriert.\n"
            "Bitte zuerst plattenkalibrierung_setzen(...) aufrufen."
        )

    return _plattenkalibrierung
_plattenkoordinate_pruefen() – Prüft, ob Spalte und Zeile innerhalb des Lochfeldes liegen.
def _plattenkoordinate_pruefen(spalte, zeile):
    """Prüft, ob die angegebene Position innerhalb des Lochfeldes liegt."""

    if not 1 <= spalte <= PLATTE_SPALTEN:
        raise ValueError(
            f"Spalte {spalte} liegt außerhalb der Platte. "
            f"Erlaubt sind Werte von 1 bis {PLATTE_SPALTEN}."
        )

    if not 1 <= zeile <= PLATTE_ZEILEN:
        raise ValueError(
            f"Zeile {zeile} liegt außerhalb der Platte. "
            f"Erlaubt sind Werte von 1 bis {PLATTE_ZEILEN}."
        )
_platten_rastervektoren() – Liefert die kalibrierten XY-Vektoren für einen Rasterabstand.
def _platten_rastervektoren():
    """Liefert die kalibrierten Dobot-XY-Vektoren eines Rasterabstands."""

    k = _plattenkalibrierung_holen()
    return k["spaltenvektor"], k["zeilenvektor"]
_loch_zu_dobot() – Rechnet Lochrasterkoordinaten in absolute Dobot-Koordinaten um.
def _loch_zu_dobot(spalte, zeile, hoehe=30.0, r=None):
    """Rechnet Lochrasterkoordinaten in Dobot-Koordinaten um."""

    spalte = float(spalte)
    zeile = float(zeile)
    hoehe = float(hoehe)

    _plattenkoordinate_pruefen(spalte, zeile)

    k = _plattenkalibrierung_holen()
    spaltenvektor, zeilenvektor = _platten_rastervektoren()

    x11, y11 = k["loch_1_1"]

    spaltenschritte = spalte - 1.0
    zeilenschritte = zeile - 1.0

    x = (
        x11
        + spaltenschritte * spaltenvektor[0]
        + zeilenschritte * zeilenvektor[0]
    )

    y = (
        y11
        + spaltenschritte * spaltenvektor[1]
        + zeilenschritte * zeilenvektor[1]
    )

    z = k["platten_z"] + hoehe
    rotation = k["standard_r"] if r is None else float(r)

    return x, y, z, rotation
_dobot_zu_platte() – Rechnet Dobot-Koordinaten in Spalte, Zeile und Höhe zurück.
def _dobot_zu_platte(x, y, z):
    """Rechnet Dobot-Koordinaten in Lochrasterkoordinaten zurück."""

    k = _plattenkalibrierung_holen()
    spaltenvektor, zeilenvektor = _platten_rastervektoren()

    x11, y11 = k["loch_1_1"]

    px = float(x) - x11
    py = float(y) - y11

    sx, sy = spaltenvektor
    zx, zy = zeilenvektor

    determinante = sx * zy - sy * zx

    if abs(determinante) < 1e-9:
        raise RuntimeError(
            "Die Plattenkalibrierung ist ungültig. "
            "Die beiden Rasterrichtungen sind nicht unabhängig."
        )

    # Lösung des linearen Gleichungssystems:
    #
    # [sx  zx] [a] = [px]
    # [sy  zy] [b]   [py]
    #
    # a und b sind die Rasterabstände vom Loch (1, 1).
    a = (px * zy - py * zx) / determinante
    b = (sx * py - sy * px) / determinante

    spalte = a + 1.0
    zeile = b + 1.0
    hoehe = float(z) - k["platten_z"]

    return spalte, zeile, hoehe

Zusammenhang der Funktionen

start.py ├─ dobot.version() ├─ dobot.init() ├─ dobot.alarme_loeschen(api) ├─ dobot.queue_starten(api) │ ├─ dobot.home(api) │ ├─ dobot.fahre_zu(...) │ ├─ dobot.fahre_zu_loch(...) │ └─ dobot.fahre_zu_loch_linear(...) └─ dobot.queue_stoppen(api) Lochraster: plattenkalibrierung_setzen() ↓ _loch_zu_dobot() / _dobot_zu_platte() ↓ fahre_zu_loch() / aktuelle_plattenposition()

Vollständiger Quelltext von dobot.py Version 1.0.0

Der folgende Quelltext wurde direkt aus der für diese Dokumentation verwendeten aktuellen Datei übernommen.

"""
Hilfsfunktionen zur Steuerung eines Dobot Magician auf Basis von DobotDllType.py.

Version: 1.0.0
Stand: 15.07.2026

Die Datei liegt im Hauptordner ``Dobot_Python``. Die 64-Bit-Version des
Dobot-SDK wird aus dem Unterordner ``sdk64`` geladen.
"""

from pathlib import Path
import ctypes
import os
import platform
import sys

from sdk64 import DobotDllType as dType


VERSION = "1.0.0"
VERSIONSDATUM = "15.07.2026"


def version():
    """Gibt die Versionsbezeichnung dieser ``dobot.py`` zurück.

    Beispiel:
        ``print(dobot.version())``
    """

    return f"dobot.py Version {VERSION} - Stand {VERSIONSDATUM}"


def com_ports_ermitteln():
    """Gibt die vom Betriebssystem erkannten seriellen Schnittstellen zurück.

    Das Ergebnis ist eine Liste aus Paaren:
    ``[(Portname, Beschreibung), ...]``.

    Wenn PySerial installiert ist, werden dessen ausführliche Angaben
    verwendet. Unter Windows dient andernfalls die Registrierungsdatenbank
    als Fallback.
    """

    try:
        from serial.tools import list_ports
    except ImportError:
        list_ports = None

    if list_ports is not None:
        ports = [
            (port.device, port.description or "Keine Beschreibung")
            for port in list_ports.comports()
        ]
        return sorted(ports, key=lambda eintrag: eintrag[0].upper())

    # Fallback ohne PySerial für Windows.
    if os.name == "nt":
        try:
            import winreg

            schluessel = winreg.OpenKey(
                winreg.HKEY_LOCAL_MACHINE,
                r"HARDWARE\DEVICEMAP\SERIALCOMM",
            )

            ports = []
            index = 0

            while True:
                try:
                    _, portname, _ = winreg.EnumValue(schluessel, index)
                    ports.append((portname, "Serielle Schnittstelle"))
                    index += 1
                except OSError:
                    break

            winreg.CloseKey(schluessel)
            return sorted(
                set(ports),
                key=lambda eintrag: eintrag[0].upper(),
            )

        except OSError:
            return []

    return []


def comport_pruefen(comport):
    """Prüft, ob der gewünschte COM-Port vom Betriebssystem erkannt wird.

    Ist der Port nicht vorhanden, werden alle erkannten seriellen
    Schnittstellen ausgegeben und das Programm mit Fehlercode 1 beendet.
    """

    ports = com_ports_ermitteln()
    vorhandene_portnamen = {
        portname.upper()
        for portname, _ in ports
    }

    if comport.upper() in vorhandene_portnamen:
        print(f"Serielle Schnittstelle: {comport} ist vorhanden.")
        return

    print()
    print(f"FEHLER: Die serielle Schnittstelle {comport} ist nicht verfügbar.")
    print()

    if ports:
        print("Vom Betriebssystem erkannte COM-Ports:")

        for portname, beschreibung in ports:
            print(f"  {portname:<8} {beschreibung}")
    else:
        print("Es wurden keine seriellen Schnittstellen gefunden.")

    print()
    print("Das Programm wird beendet.")
    sys.exit(1)


# ---------------------------------------------------------------------------
# Lochrasterplatte
# ---------------------------------------------------------------------------

PLATTE_SPALTEN = 40
PLATTE_ZEILEN = 27
PLATTE_RASTER_MM = 16.0

# Die Kalibrierung wird mit plattenkalibrierung_setzen(...) eingetragen.
# Benötigt werden die Dobot-Koordinaten von drei beliebigen erreichbaren
# Referenzlöchern. Ihre Rasterpositionen dürfen nicht auf einer Geraden liegen.
_plattenkalibrierung = None


def _referenzloch_lesen(referenz, bezeichnung):
    """Prüft und zerlegt ein Referenzloch.

    Neue Schreibweise:
        ``(spalte, zeile, x, y)``

    Für die bisherige Eckpunktkalibrierung wird zusätzlich die alte
    Schreibweise ``(x, y)`` unterstützt.
    """

    try:
        werte = tuple(referenz)
    except TypeError as exc:
        raise TypeError(
            f"{bezeichnung} muss ein Tupel oder eine Liste sein."
        ) from exc

    if len(werte) != 4:
        raise ValueError(
            f"{bezeichnung} muss als "
            "(spalte, zeile, x, y) angegeben werden."
        )

    spalte, zeile, x, y = map(float, werte)
    _plattenkoordinate_pruefen(spalte, zeile)

    return spalte, zeile, x, y


def plattenkalibrierung_setzen(
    referenzloch_1,
    referenzloch_2,
    referenzloch_3,
    platten_z,
    standard_r=0.0,
):
    """Kalibriert die Platte mit drei beliebigen erreichbaren Löchern.

    Neue Schreibweise der Referenzpunkte:
        ``(spalte, zeile, x, y)``

    Beispiel:
        ``referenzloch_1=(2, 1, -73.8, -311.1)``

    Die drei Rasterpositionen dürfen nicht auf einer gemeinsamen Geraden
    liegen. Günstig sind zwei weit auseinanderliegende Löcher einer Zeile
    und ein drittes Loch in einer möglichst weit entfernten Zeile.

    Aus Gründen der Abwärtskompatibilität wird auch die bisherige
    Eckpunkt-Schreibweise mit drei XY-Tupeln unterstützt:

        ``loch_1_1=(x, y)``
        ``loch_40_1=(x, y)``
        ``loch_1_27=(x, y)``
    """

    global _plattenkalibrierung

    # Alte Schreibweise erkennen:
    # (x, y), (x, y), (x, y)
    alte_schreibweise = all(
        hasattr(ref, "__len__") and len(ref) == 2
        for ref in (
            referenzloch_1,
            referenzloch_2,
            referenzloch_3,
        )
    )

    if alte_schreibweise:
        referenzloch_1 = (
            1,
            1,
            referenzloch_1[0],
            referenzloch_1[1],
        )
        referenzloch_2 = (
            PLATTE_SPALTEN,
            1,
            referenzloch_2[0],
            referenzloch_2[1],
        )
        referenzloch_3 = (
            1,
            PLATTE_ZEILEN,
            referenzloch_3[0],
            referenzloch_3[1],
        )

    a = _referenzloch_lesen(referenzloch_1, "referenzloch_1")
    b = _referenzloch_lesen(referenzloch_2, "referenzloch_2")
    c = _referenzloch_lesen(referenzloch_3, "referenzloch_3")

    spalte_a, zeile_a, x_a, y_a = a
    spalte_b, zeile_b, x_b, y_b = b
    spalte_c, zeile_c, x_c, y_c = c

    ds_b = spalte_b - spalte_a
    dz_b = zeile_b - zeile_a
    ds_c = spalte_c - spalte_a
    dz_c = zeile_c - zeile_a

    determinante = ds_b * dz_c - ds_c * dz_b

    if abs(determinante) < 1e-9:
        raise ValueError(
            "Die drei Referenzlöcher sind für eine Kalibrierung "
            "ungeeignet: Ihre Rasterpositionen liegen auf einer Geraden."
        )

    dx_b = x_b - x_a
    dx_c = x_c - x_a
    dy_b = y_b - y_a
    dy_c = y_c - y_a

    # Dobot-Vektor für einen Schritt in Spaltenrichtung.
    sx = (dx_b * dz_c - dx_c * dz_b) / determinante
    sy = (dy_b * dz_c - dy_c * dz_b) / determinante

    # Dobot-Vektor für einen Schritt in Zeilenrichtung.
    zx = (ds_b * dx_c - ds_c * dx_b) / determinante
    zy = (ds_b * dy_c - ds_c * dy_b) / determinante

    # Aus dem erreichbaren Referenzloch A wird rechnerisch die Position
    # des möglicherweise nicht erreichbaren Lochs (1, 1) bestimmt.
    x_1_1 = (
        x_a
        - (spalte_a - 1.0) * sx
        - (zeile_a - 1.0) * zx
    )
    y_1_1 = (
        y_a
        - (spalte_a - 1.0) * sy
        - (zeile_a - 1.0) * zy
    )

    _plattenkalibrierung = {
        "loch_1_1": (x_1_1, y_1_1),
        "spaltenvektor": (sx, sy),
        "zeilenvektor": (zx, zy),
        "referenzloecher": (a, b, c),
        "platten_z": float(platten_z),
        "standard_r": float(standard_r),
    }



def _plattenkalibrierung_holen():
    """Liefert die Kalibrierung oder erzeugt eine verständliche Meldung."""

    if _plattenkalibrierung is None:
        raise RuntimeError(
            "Die Lochrasterplatte ist noch nicht kalibriert.\n"
            "Bitte zuerst plattenkalibrierung_setzen(...) aufrufen."
        )

    return _plattenkalibrierung


def _plattenkoordinate_pruefen(spalte, zeile):
    """Prüft, ob die angegebene Position innerhalb des Lochfeldes liegt."""

    if not 1 <= spalte <= PLATTE_SPALTEN:
        raise ValueError(
            f"Spalte {spalte} liegt außerhalb der Platte. "
            f"Erlaubt sind Werte von 1 bis {PLATTE_SPALTEN}."
        )

    if not 1 <= zeile <= PLATTE_ZEILEN:
        raise ValueError(
            f"Zeile {zeile} liegt außerhalb der Platte. "
            f"Erlaubt sind Werte von 1 bis {PLATTE_ZEILEN}."
        )


def _platten_rastervektoren():
    """Liefert die kalibrierten Dobot-XY-Vektoren eines Rasterabstands."""

    k = _plattenkalibrierung_holen()
    return k["spaltenvektor"], k["zeilenvektor"]


def _loch_zu_dobot(spalte, zeile, hoehe=30.0, r=None):
    """Rechnet Lochrasterkoordinaten in Dobot-Koordinaten um."""

    spalte = float(spalte)
    zeile = float(zeile)
    hoehe = float(hoehe)

    _plattenkoordinate_pruefen(spalte, zeile)

    k = _plattenkalibrierung_holen()
    spaltenvektor, zeilenvektor = _platten_rastervektoren()

    x11, y11 = k["loch_1_1"]

    spaltenschritte = spalte - 1.0
    zeilenschritte = zeile - 1.0

    x = (
        x11
        + spaltenschritte * spaltenvektor[0]
        + zeilenschritte * zeilenvektor[0]
    )

    y = (
        y11
        + spaltenschritte * spaltenvektor[1]
        + zeilenschritte * zeilenvektor[1]
    )

    z = k["platten_z"] + hoehe
    rotation = k["standard_r"] if r is None else float(r)

    return x, y, z, rotation


def _dobot_zu_platte(x, y, z):
    """Rechnet Dobot-Koordinaten in Lochrasterkoordinaten zurück."""

    k = _plattenkalibrierung_holen()
    spaltenvektor, zeilenvektor = _platten_rastervektoren()

    x11, y11 = k["loch_1_1"]

    px = float(x) - x11
    py = float(y) - y11

    sx, sy = spaltenvektor
    zx, zy = zeilenvektor

    determinante = sx * zy - sy * zx

    if abs(determinante) < 1e-9:
        raise RuntimeError(
            "Die Plattenkalibrierung ist ungültig. "
            "Die beiden Rasterrichtungen sind nicht unabhängig."
        )

    # Lösung des linearen Gleichungssystems:
    #
    # [sx  zx] [a] = [px]
    # [sy  zy] [b]   [py]
    #
    # a und b sind die Rasterabstände vom Loch (1, 1).
    a = (px * zy - py * zx) / determinante
    b = (sx * py - sy * px) / determinante

    spalte = a + 1.0
    zeile = b + 1.0
    hoehe = float(z) - k["platten_z"]

    return spalte, zeile, hoehe


def plattenkalibrierung_anzeigen():
    """Zeigt Referenzlöcher, Ursprung und Rastervektoren an."""

    k = _plattenkalibrierung_holen()
    spaltenvektor, zeilenvektor = _platten_rastervektoren()

    print("Kalibrierung der Lochrasterplatte")
    print("---------------------------------")

    for nummer, referenz in enumerate(k["referenzloecher"], start=1):
        spalte, zeile, x, y = referenz
        print(
            f"Referenz {nummer}: "
            f"Loch ({spalte:g}, {zeile:g}) -> "
            f"X={x:.2f}, Y={y:.2f}"
        )

    x11, y11 = k["loch_1_1"]

    print()
    print(
        "Rechnerisches Loch (1, 1): "
        f"X={x11:.2f}, Y={y11:.2f}"
    )
    print(f"Platten-Z:   {k['platten_z']:.2f} mm")
    print(f"Standard-R:  {k['standard_r']:.2f}°")
    print()
    print(
        "Rastervektor Spalte: "
        f"dx={spaltenvektor[0]:.4f}, "
        f"dy={spaltenvektor[1]:.4f}"
    )
    print(
        "Rastervektor Zeile:  "
        f"dx={zeilenvektor[0]:.4f}, "
        f"dy={zeilenvektor[1]:.4f}"
    )




# Das von os.add_dll_directory() gelieferte Objekt muss erhalten bleiben,
# solange die DLL verwendet wird.
_dll_suchpfad = None


def init(
    comport="COM10",
    device_name=(
        "Dobot Magician - AG Young Engineers - "
        "Martin-Rinckart-Gymnasium"
    ),
):
    """Lädt die 64-Bit-DLL, verbindet den Dobot und gibt ``api`` zurück."""

    global _dll_suchpfad

    hauptverzeichnis = Path(__file__).resolve().parent
    sdk_verzeichnis = hauptverzeichnis / "sdk64"
    dll_datei = sdk_verzeichnis / "DobotDll.dll"

    print("Python-Version:    ", platform.python_version())
    print("Python-Architektur:", platform.architecture()[0])
    print("SDK-Verzeichnis:   ", sdk_verzeichnis)
    print("DLL-Datei:         ", dll_datei)
    print("DLL vorhanden:     ", dll_datei.exists())

    # Vor dem Laden der DLL prüfen, ob der gewünschte COM-Port existiert.
    comport_pruefen(comport)

    if not dll_datei.exists():
        raise FileNotFoundError(
            "Die Dobot-DLL wurde nicht gefunden:\n"
            f"{dll_datei}\n\n"
            "Erwartete Ordnerstruktur:\n"
            "Dobot_Python\\\n"
            "├── dobot.py\n"
            "├── sdk64\\\n"
            "│   ├── DobotDll.dll\n"
            "│   └── DobotDllType.py\n"
            "└── projekt01\\\n"
            "    └── start.py"
        )

    # Unter Windows können sich in sdk64 weitere benötigte DLLs befinden.
    if hasattr(os, "add_dll_directory"):
        _dll_suchpfad = os.add_dll_directory(str(sdk_verzeichnis))

    # DobotDll.dll über ihren vollständigen Pfad laden.
    api = ctypes.CDLL(str(dll_datei))

    result = dType.ConnectDobot(api, comport, 115200)
    print("ConnectDobot-Ergebnis:", result)

    # 0 bedeutet bei der Dobot-API: Verbindung erfolgreich.
    if result[0] != 0:
        meldungen = {
            1: "Dobot wurde nicht gefunden.",
            2: "Der COM-Port ist bereits belegt.",
        }
        meldung = meldungen.get(result[0], "Unbekannter Verbindungsfehler.")
        raise ConnectionError(
            f"Verbindung über {comport} fehlgeschlagen: {meldung} "
            f"(Fehlercode {result[0]})"
        )

    if device_name:
        dType.SetDeviceName(api, device_name)

    name = dType.GetDeviceName(api)
    seriennummer = dType.GetDeviceSN(api)

    print("Gerätename:   ", name)
    print("Seriennummer: ", seriennummer)

    return api


def warten_bis_fertig(api, ziel_index):
    """Wartet, bis ein Queue-Befehl vollständig ausgeführt wurde."""

    while dType.GetQueuedCmdCurrentIndex(api)[0] < ziel_index:
        dType.dSleep(100)


def queue_starten(api):
    """Stoppt eine laufende Queue, löscht sie und startet sie neu."""

    dType.SetQueuedCmdStopExec(api)
    dType.SetQueuedCmdClear(api)
    dType.SetQueuedCmdStartExec(api)


def queue_stoppen(api):
    """Stoppt die Ausführung der Queue."""

    dType.SetQueuedCmdStopExec(api)


def position_lesen(api):
    """Liest die kartesische Position X, Y, Z und R."""

    return dType.GetPose(api)[:4]


def position_anzeigen(api):
    """Liest und zeigt die aktuelle kartesische Position an."""

    x, y, z, r = position_lesen(api)

    print(
        f"X={x:.1f} mm, "
        f"Y={y:.1f} mm, "
        f"Z={z:.1f} mm, "
        f"R={r:.1f}°"
    )


def fahre_zu(api, x, y, z, r, modus=None):
    """Fährt zu einer Zielposition und wartet auf das Bewegungsende."""

    if modus is None:
        modus = dType.PTPMode.PTPMOVJXYZMode

    ziel_index = dType.SetPTPCmd(
        api,
        modus,
        x,
        y,
        z,
        r,
        isQueued=1,
    )[0]

    warten_bis_fertig(api, ziel_index)


def fahre_zu_loch(
    api,
    spalte,
    zeile,
    hoehe=30.0,
    r=None,
):
    """Fährt gelenkoptimiert zu einer Position im Lochraster.

    ``spalte`` und ``zeile`` beginnen bei 1.

    ``hoehe`` ist die Höhe in Millimetern über der Plattenoberfläche.
    Der sichere Standardwert beträgt 30 mm.

    Beispiel:
        ``fahre_zu_loch(api, 10, 8, hoehe=30)``
    """

    x, y, z, rotation = _loch_zu_dobot(
        spalte=spalte,
        zeile=zeile,
        hoehe=hoehe,
        r=r,
    )

    fahre_zu(
        api,
        x,
        y,
        z,
        rotation,
        dType.PTPMode.PTPMOVJXYZMode,
    )

    return x, y, z, rotation


def fahre_zu_loch_linear(
    api,
    spalte,
    zeile,
    hoehe=30.0,
    r=None,
):
    """Fährt auf einer geraden Bahn zu einer Position im Lochraster.

    Der gesamte Weg wird linear ausgeführt. Vor allem bei niedriger Höhe
    muss deshalb geprüft werden, ob der Fahrweg frei ist.

    Beispiel:
        ``fahre_zu_loch_linear(api, 10, 8, hoehe=5)``
    """

    x, y, z, rotation = _loch_zu_dobot(
        spalte=spalte,
        zeile=zeile,
        hoehe=hoehe,
        r=r,
    )

    fahre_zu(
        api,
        x,
        y,
        z,
        rotation,
        dType.PTPMode.PTPMOVLXYZMode,
    )

    return x, y, z, rotation


def aktuelle_plattenposition(api, anzeigen=True):
    """Liefert die aktuelle Position im Lochraster-Koordinatensystem.

    Rückgabe:
        ``(spalte, zeile, hoehe)``

    Die Werte für Spalte und Zeile können Dezimalstellen enthalten.
    Ein ganzzahliger Wert bezeichnet die Mitte eines Lochs.

    Mit ``anzeigen=False`` wird die Ausgabe unterdrückt.
    """

    x, y, z, _r = position_lesen(api)
    spalte, zeile, hoehe = _dobot_zu_platte(x, y, z)

    if anzeigen:
        naechste_spalte = round(spalte)
        naechste_zeile = round(zeile)

        print("Aktuelle Plattenposition:")
        print(f"  Spalte: {spalte:.3f}")
        print(f"  Zeile:  {zeile:.3f}")
        print(f"  Höhe:   {hoehe:.2f} mm")
        print(
            "  Nächstes Loch: "
            f"({naechste_spalte}, {naechste_zeile})"
        )

    return spalte, zeile, hoehe


def home(api):
    """Führt eine HOME-Fahrt aus und wartet auf deren Abschluss."""

    print("HOME-Fahrt wird gestartet.")

    ziel_index = dType.SetHOMECmd(
        api,
        0,
        isQueued=1,
    )[0]

    warten_bis_fertig(api, ziel_index)

    print("HOME-Fahrt abgeschlossen.")


def test_z(api):
    """Erlaubt das interaktive Verändern der aktuellen Z-Koordinate."""

    print()
    print("Interaktiver Test der Z-Koordinate")
    print("----------------------------------")

    while True:
        x, y, z, r = position_lesen(api)

        print(
            f"Position: X={x:.1f}, Y={y:.1f}, "
            f"Z={z:.1f}, R={r:.1f}"
        )

        eingabe = input(
            "Neue Z-Koordinate oder 'a' zum Abbrechen: "
        ).strip()

        if eingabe.lower() == "a":
            break

        # Auch eine Eingabe mit deutschem Dezimalkomma wird akzeptiert.
        try:
            neue_z = float(eingabe.replace(",", "."))
        except ValueError:
            print("Ungültige Eingabe. Bitte eine Zahl oder 'a' eingeben.")
            continue

        fahre_zu(
            api,
            x,
            y,
            neue_z,
            r,
            dType.PTPMode.PTPMOVLXYZMode,
        )

        print("Erreichte Position:")
        position_anzeigen(api)

    print("Z-Test beendet.")


def ausfuehren(api):
    """Startet den auf der Webseite beschriebenen Beispielablauf."""

    print()
    print("Roboterprogramm wird gestartet.")
    print("--------------------------------")

    queue_starten(api)

    try:
        print("Aktuelle Position:")
        position_anzeigen(api)

        test_z(api)
    finally:
        queue_stoppen(api)

        print()
        print("Roboterprogramm beendet.")


# ---------------------------------------------------------------------------
# Weitere Funktionen
# ---------------------------------------------------------------------------

def alarme_loeschen(api):
    """Löscht alle gespeicherten Alarmzustände des Dobot.

    Achtung:
    Besteht die Ursache weiterhin, wird der Alarm erneut ausgelöst.
    """

    if api is None:
        raise RuntimeError("Der Dobot ist nicht verbunden.")

    dType.ClearAllAlarmsState(api)


def main():
    """Hinweis beim direkten Start dieser Bibliotheksdatei."""

    print(
        "Diese Datei wird normalerweise nicht direkt gestartet.\n"
        "Bitte 'start.py' ausführen."
    )


if __name__ == "__main__":
    main()