Hochschule für Technik und Wirtschaft Dresden Fachbereich Informatik/Mathematik Diplomarbeit im Studiengang Informatik Thema: Konzeption und Aufbau eines modular erweiterbaren Programmsystems zur Simulation von Robotern eingereicht von: René Liebscher eingereicht am: 28.08.2000 Betreuer: Prof. Iwe Wenn man Algorithmen und Techniken zur Steuerung von Robotern erforschen möchte, kann man zwei Wege beschreiten. Man kann sich einen Roboter bauen oder kaufen, diesen dann programmieren und die Steueralgorithmen anhand verschiedener Testläufe untersuchen und analysieren. Oder man besitzt einen Simulator, der den Roboter und dessen Umwelt ausreichend genau simuliert, und testet seine Programme dort. Der zweite Ansatz hat einige Vorteile, zuallererst fallen die Hardwarekosten weg, man kann mehrere Algorithmen gleichzeitig testen, wenn man zum Beipiel mehrere Simulatoren auf mehreren Computern laufen läßt. Wenn Scriptunterstützung oder ähnliche Features im Simulator vorhanden sind, kann man komplette Versuchsreihen automatisiert ablaufen lassen. Weiterhin kann eine Simulation schneller sein als ein echter Roboter, weil dessen mechanische Trägheit in der Simulation nicht als begrenzender Faktor auftritt, und nicht zuletzt kann man, wenn die Software dafür ausgelegt ist, einen simulierten Roboter leichter und billiger mit neuen Sensoren ausstatten. So kann man erst einmal in der Simulation testen, ob bestimmte Dinge für den realen Roboter Vorteile bringen könnten. Damit dies aber möglich ist, muß der Simulator modular aufgebaut sein, denn sonst ist eine Erweiterbarkeit kaum machbar. Es gibt bereits einige Simulatoren für den autonomen Miniaturroboter Khepera, aber dort sind zum Teil einige der Vorteile eines Simulators durch andere Einschränkungen nicht nutzbar. Als prinzipielles Problem muß man hier die Nichtverfügbarkeit der Quelltexte nennen, weil entweder nicht freigegeben oder der Simulator an sich kommerziell vertrieben wird. Hätte man die Quelltexte, könnte man verschiedene Nachteile dieser Simulatoren leicht beheben, so wäre es dann kein Problem andere Sensorarten zu kreieren oder gar komplett andere Arten von Robotern zu simulieren, zum Beispiel solche mit Beinen statt Rädern. Die vorliegende Arbeit soll das Konzept eines Simulators vorstellen, der viele der genannten Vorteile eines idealen Simulators in sich vereint, und dabei trotzdem versucht die möglichen Nachteile zu vermeiden. Das bedeutet unter anderem, daß alle Quelltexte frei verfügbar sind und auch alle anderen benutzten Softwarepakete aus dem Bereich der freien Software entstammen. Das geht hin bis zu der Tatsache, daß auch zur Erstellung von Erweiterungsmodulen die Arbeit komplett auf freier Software basieren kann, zum Beispiel dem GNU C-Compiler für Windows anstatt auf Microsoft’s Visual C++ zu bestehen. Das System soll vollständig modular aufgebaut sein, so daß man leicht bestehende Module ergänzen oder austauschen kann beziehungsweise durch neue Module auch neue Funktionalität hinzufügen kann. Als wahrscheinlich interessanteste neue Eigenschaft soll der Simulator komplett durch eine Scriptsprache steuerbar sein, dies ermöglicht dann erstmals wirklich über lange Zeit laufende Simulationen ernsthaft in Erwägung zu ziehen. Wenn man die Möglichkeit hat, solche Simulationen vollautomatisch zum Beispiel über’s Wochenende ablaufen zu lassen, ergeben sich ganz neue Perspektiven in Hinsicht auf die Machbarkeit von Algorithmen, die sehr lange Zeit zum Lernen brauchen. Das betrifft insbesondere genetische Algorithmen, die in dieser Hinsicht sehr anspruchsvoll sind. Inhaltsverzeichnis I. Grundlagen 1. Einleitung 3 1.1. Geschichte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.1.1. Projektseminar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.1.2. Internationaler Khepera Workshop . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2. Der neue Simulator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.3. Über diese Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2. Systemanforderungen 6 2.1. Der ideale Simulator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.1. Simulation der Welt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.2. Zeitablauf der Steuerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1.3. Erweiterbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1.4. Automatisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2. Bisher verfügbare Systeme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2.1. Webots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2.2. LV3D/Easybot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.3. Eigenschaften des geplanten Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 3. Grundstrukturen im System ii 1 10 3.1. Interne Datenstruktur und Modularität . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.2. Abbildung auf das externe Datenformat . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.3. Signale und Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 3.4. Steuerung des Zeitablaufs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.4.1. Prinzipielle Möglichkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.4.2. Beschreibung der gewählten Variante . . . . . . . . . . . . . . . . . . . . . . . 14 3.5. Benutzung mehrerer Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Inhaltsverzeichnis 4. Auswahl der Software 18 4.1. Grundvoraussetzungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.2. 3D-Modell und Visualisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.3. Grafische Benutzeroberfläche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.4. Scriptsprache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 5. Python 23 5.1. Die Programmiersprache Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 5.1.1. Eine kurze Sprachvorstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 5.1.2. Anwendungsbereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.1.3. Entwicklungsstand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.2. Erweiterungsmodule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 5.2.1. distutils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 5.2.2. pygtk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 5.2.3. PyOpenGL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 5.2.4. Numerical Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 II. Realisierung 29 6. Übersicht 31 6.1. Python-Integration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 6.2. Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 6.2.1. Basis-Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 6.2.2. Erweiterungsmodule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 6.3. Das zentrale Repository . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 6.3.1. Allgemeine Modul-Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . 34 6.3.2. XML-Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 6.3.3. Konfigurationsdialoge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 7. RSTk-Module 37 7.1. Basis-Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 7.1.1. rstk_threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 7.1.2. unique_string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 7.1.3. callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 7.1.4. nodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 7.1.5. scheduler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 iii Inhaltsverzeichnis 7.1.6. dll . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 7.2. Erweiterungsmodule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 7.2.1. 3D-Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 7.2.2. 3D-Visualisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 7.2.3. Khepera-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 8. Programmoberfläche 8.1. Hauptfenster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 8.1.1. Menüpunkte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 8.1.2. Toolbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 8.1.3. Sonstige Anzeige- und Bedienelemente . . . . . . . . . . . . . . . . . . . . . . 46 8.2. Konfigurationsdialoge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 8.2.1. Für das gesamte Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 8.2.2. Für einzelne Instanzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 8.3. 3D-Ansicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 9. Simulation des Khepera 50 9.1. Arbeitsweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 9.2. Realisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 9.2.1. Die BIOS-Umleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 9.2.2. Die Khepera-BIOS-Simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 9.2.3. Das Khepera-Controller-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . 55 9.3. Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 9.3.1. Quelltext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 9.3.2. Erstellen der Controller-DLL . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 9.3.3. Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 10. Entwicklungsstand 60 10.1. Stand der Entwicklung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 10.2. Portabilität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 10.3. Bekannte Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 10.3.1. pygtk und GTK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 III. Erweiterungen iv 44 63 Inhaltsverzeichnis 11. Weiterer Ausbau 65 11.1. Notwendige Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 11.1.1. Graphischer Szeneneditor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 11.1.2. Kollisionserkennung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 11.1.3. Dynamiksimulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 11.2. Weitere Vorschläge für Erweiterungsmodule . . . . . . . . . . . . . . . . . . . . . . . . 66 11.2.1. GPS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 11.2.2. 3D-Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 11.3. Scriptgesteuerte Anwendungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 11.3.1. Hintergrundbetrieb selbstlernender Controller . . . . . . . . . . . . . . . . . . . 67 11.3.2. Genetische Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 11.3.3. Vergleich verschiedener Steueralgorithmen oder Umgebungen . . . . . . . . . . 68 11.3.4. Turnierbetrieb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 12. Erstellung eines Erweiterungsmoduls 69 12.1. Das Tracker-Modul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 12.1.1. Aufgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 12.1.2. Vorgehensweise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 12.2. Implementierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 12.2.1. Python-Objekte in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 12.2.2. Zeitkritische Teile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 12.2.3. Modulinterface in Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 12.3. Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 12.4. Ergebnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 IV. Sonstiges 75 13. Nutzung und Verwertung 77 14. Selbstständigkeitserklärung 78 15. Thesen 79 V. 81 Anhang v Inhaltsverzeichnis A. Installation des Systems 83 A.1. Notwendige externe Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 A.1.1. Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 A.1.2. Distutils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 A.1.3. PyOpenGL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 A.1.4. pygtk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 A.2. Die eigentliche Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 A.3. Kurztest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 A.4. Test-Installationen auf CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 B. Inhalt der CD 86 C. Beispiel XML-Daten 87 D. Sonstige Quelltexte 93 D.1. build_dll.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 D.2. Quelltexte zum Trackermodul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 D.2.1. tracker.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 D.2.2. _tracker.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 D.2.3. tracker.py . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 Glossar 106 Quellenverzeichnis 109 vi Abbildungsverzeichnis 3.1. Allgemeine Baumstruktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.2. Aufbau einer einzelnen Node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.3. Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 3.4. Zeitablauf bei eventgesteuerter Simulation . . . . . . . . . . . . . . . . . . . . . . . . . 16 6.1. Gesamtübersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 6.2. Aufbau eines RSTk-Moduls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 6.3. Zusammenarbeit der Dateien eines Konfigurationsdialoges . . . . . . . . . . . . . . . . 36 8.1. Hauptbildschirm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 8.2. Info-Bildschirm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 8.3. Modul-Konfigurationsdialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 8.4. Nodes-Konfigurationsdialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 8.5. 3D-Ansichtsfenster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 9.1. Steuerungsprogramm direkt im Roboter . . . . . . . . . . . . . . . . . . . . . . . . . . 50 9.2. Steuerung des Khepera über das serielle Interface . . . . . . . . . . . . . . . . . . . . . 50 9.3. Verbindung des Steuerprogrammes mit dem Simulator . . . . . . . . . . . . . . . . . . 51 9.4. Anwendung der Schnittstelle für andere Zwecke . . . . . . . . . . . . . . . . . . . . . . 52 9.6. Konfigurationsdialog des Khepera-Controller-Moduls . . . . . . . . . . . . . . . . . . . 59 11.1. Inhalt des Z-Buffers nach dem Rendern . . . . . . . . . . . . . . . . . . . . . . . . . . 66 12.1. Konfigurationsdialog des Tracker-Moduls . . . . . . . . . . . . . . . . . . . . . . . . . 74 12.2. Ansicht bei angezeigter Spur des Tracker-Moduls . . . . . . . . . . . . . . . . . . . . . 74 vii Teil I. Grundlagen 1 1. Einleitung 1.1. Geschichte Dieser Abschnitt berichtet, wie es überhaupt dazu kam, daß ich mich mit Robotern und deren Simulation beschäftige. Außerdem kann man hier erfahren, welchen Umständen die Idee für diesen RoboterSimulator entsprang. 1.1.1. Projektseminar Im 7. Semester wurde im Rahmen des Studienganges „Informatik“ das Projektseminar „Roboterprogrammierung“[17] veranstaltet. Das Ziel des Projektseminars war unter anderem die Entwicklung von Steueralgorithmen für den Miniaturroboter Khepera und die Nutzung und Weiterentwicklung des Robotersimulators Easybot. Während mit dem Roboter selbst kaum gearbeitet wurde, so entstanden doch verschiedene Programme für den Simulator. Darunter waren ein Programm bei dem der Roboter einer Linie auf dem Boden folgte und mehrere Programme um den Roboter durch ein Labyrinth zu steuern. Bei diesen LabyrinthProgrammen wurden zwei verschiedene Wege beschritten. Bei einen wurde die Steuerung erst von Hand programmiert, dann deren Daten aufgezeichnet und damit ein neuronales Netz trainiert. Ein anderer Weg bestand darin das neuronale Netz direkt mittels genetischem Algorithmus zu trainieren. Einer dieser genetischen Algorithmen wurde von mir programmiert, um damit einige Experimente in der Simulation zu wiederholen, die ursprünglich von den Kheperaentwicklern mit den echten Robotern durchgeführt wurden. (Die entsprechenden Dokumente sind auch auf der, als Ergebnis der Projektseminars entstandenen, CD[18] enthalten.) Dabei stellten sich einige Unzulänglichkeiten des Simulators heraus. • Die neue Position bei Bewegung des Roboters muß vom Steueralgorithmus selbst berechnet werden. Allerdings ist keine Information vorhanden, wie lang die Zeitabstände zwischen zwei Aktivierungen der Steuerung durch den Simulator sind. • Wände stellen keine Hindernisse für den Roboter dar, sondern dieser fährt einfach hindurch. Dies ist insbesondere für den genetischen Algorithmus ein Problem, der so die Sensorwerte überwachen muß, um herauszubekommen, ob der Roboter eventuell mit der Wand kollidiert ist, und dann entsprechende Aktionen auszuführen hat. • Es gibt keine Verzögerung zwischen Ein- und Ausgabe. Dies ließ sich aber durch einen Trick1 doch noch realisieren, wobei sich dann natürlich ein entsprechend anderes Verhalten der Roboter 1 Dabei wird die Ausgabe einfach erst bei der jeweils nachfolgenden Aktivierung des Steuerprogrammes wirklich ausgegeben. Dies entspricht dann einer Verzögerung von Ein- zu Ausgabe um die Taktzeit. 3 1. Einleitung zeigte. Die ohne diesen Trick trainierten Roboter fuhren nach Aktivierung der Zeitverzögerung immer zu nah an die Wand heran, um dann regelrecht „zurückzuschrecken“. Da reale Roboter immer irgendwelche Verzögerungen haben, ist die Brauchbarkeit des Simulator hier beschränkt. • Da genetische Algorithmen sehr lange zur Berechnung brauchen, ist es von Vorteil, wenn die Simulation schneller als normal ablaufen kann. Dies war zu Beginn überhaupt nicht möglich, wurde aber dann noch nachgerüstet. • Man hat keine Möglichkeit den Simulator irgendwie per Programm zu beeinflussen, so daß man bei den genetischen Algorithmen selber daneben sitzen und diese überwachen muß. • Das Programm läuft nur unter Windows und in der Hochschule sind die Windows-Rechner oft nur für eine Vorlesungsstunde verfügbar, länger dauernde genetische Algorithmen sind so nicht am Stück zu berechnen. (Abgesehen davon arbeiten einige Leute lieber mit Linux.) Neben diesen allgemeinen Problemen wurden aber auch einige unnötige Probleme zusätzlich geschaffen, in dem zum Beispiel das Interface zu den Steuerungs-DLL’s ohne Vorwarnung verändert wurde. Letztendlich führte das auch dazu, daß im Abschlußbericht der Simulator teilweise mit einer veralteten Schnittstelle beschrieben ist, während andere Teile sich bereits auf die neueste Version beziehen. 1.1.2. Internationaler Khepera Workshop Im Dezember 1999 fand in Paderborn der 1. Internationale Khepera Workshop statt, bei dem Prof. Iwe, Oliver Michel und ich den Simulator Easybot der Öffentlichkeit vorstellten. Dabei kamen die unterschiedlichsten Kommentare und Fragen zum Simulator, die meisten bezogen sich hierbei auf die freie Verfügbarkeit, die Erweiterbarkeit, das Zeitregime bei der Simulation und die Realität der Simulation allgemein. Hierzu muß man feststellen, daß der Simulator zwar frei zu haben ist, aber nicht seine Quelltexte. Dadurch ist die Erweiterbarkeit darauf beschränkt Robotersteuerungen zu programmieren, die Ergänzung weiterer Khepera-Elemente oder die Verbesserung vorhandener Sensoren ist also nicht möglich. Auch ein echtes Zeitregime existiert nicht, da man im Simulator zwar die Taktzeit vorgeben kann, aber der Controller, der die Bewegung des Roboters berechnen muß, diese nicht kennt. Dadurch ist es unmöglich eine realistische Geschwindigkeit auf den Bildschirm zu bekommen. Neben der Vorstellung unseres Simulators ergab sich aber auch die Möglichkeit, die Entwicklungen und Forschungen anderer Leute unter die Lupe zu nehmen. Einige stellten hierbei Steuerungsalgorithmen vor, andere untersuchten das Verhalten der Kheperas an sich, in dem zum Beispiel die Sensorwerte mit den realen Werten verglichen wurden und wieder andere hatten neue Hardware entwickelt, dies betraf einerseits Sensoren und andererseits Kommunikationsmöglichkeiten (zum Beispiel über Infrarot.) Einen kompletten Überblick geben hierzu die Proceedings of the 1st International Khepera Workshop[10]. Weiterhin fand im Anschluß am selben Ort auch noch die WDR-Computer-Club „Robots Night“ statt. Dort wurden viele verschiedene Anwendungen und Experimente mit Robotern vorgeführt, einschließlich eines Clustering-Versuches2 mit insgesamt 64 Kheperas. Neben den weitverbreiteten Robotern mit Rädern waren auch einige der Sony-Spielzeug-Roboter (Aibo) mit Beinen zum Fußballturnier angetreten. Insbesondere letztere sind sicher eine Herausforderung, wenn man dafür ein Simulator bauen möchte. 2 Dabei werden viele Roboter mit einem einfachen Steueralgorithmus benutzt, um ein bestimmtes Verhalten in der Gruppe zu erhalten. Bei diesem Clustering-Versuch war das Ziel zufällig verstreute Schaumstoffwürfel zu kleinen Haufen zusammenzuschieben. 4 1.2. Der neue Simulator 1.2. Der neue Simulator Aufgrund der Unzulänglichkeiten des vorhandenen Simulators entstand die Idee, einen von Grund auf neu entwickelten Simulator zu programmieren, der diese Probleme nicht haben sollte. Insbesondere sollte dieser beliebig durch den Anwender erweiterbar sein, so daß wenn sich neue Einschränkungen zeigen sollten, diese schnell beseitigt werden können und auch so jederzeit neue Elemente eingebaut werden können. Insgesamt soll der neue Simulator eine Art Baukastensystem zur Simulation verschiedenster Roboter werden. Dem entsprechend wurde für dieses Projekt der Name „Robots Simulation Toolkit“ oder kurz „RSTk“ gewählt. Dieser Name ist zwar nicht sonderlich originell, aber einerseits drückt er das Ziel des Systems, ein Baukastensystem zu sein, aus, andererseits ist dieser Name noch nicht vergeben. Viele der naheliegenden Namen wie zum Beispiel simrobot oder rosi sind bereits vergeben und allgemein sind viele Kombinationen mit robot oder bot bereits für irgendwelche Internetprogramme (Suchmaschinen) vergeben. 1.3. Über diese Arbeit Diese Diplomarbeit soll die allgemeine Struktur und den Aufbau dieses neuen Simulators verstellen. Dazu werden im ersten Teil einige grundlegende Strukturen für solch einen Simulator vorgestellt, außerdem wird die Software zur Realisierung des Simulators entsprechend der Anforderungen ausgewählt. Der zweite Teil erläutert die eigentliche Programmierung und Realisierung des Systems und der dritte Teil enthält Beispiele und Vorschläge für eigene Erweiterungen des Simulators. Leider reicht der begrenzte Platz hier nicht aus, um in diesem Rahmen alle wichtigen Eigenschaften des Systems zu beschreiben, insbesondere die Modulbeschreibungen mussten auf ein Minimum reduziert werden. Deshalb soll hier auf die Quelltexte und das Quellenverzeichnis mit seinen vielen weiterführenden Links verwiesen werden. 5 2. Systemanforderungen 2.1. Der ideale Simulator 2.1.1. Simulation der Welt Die Komplexität der Simulation der Welt wird im wesentlichen durch die zu simulierenden Sensoren und Steueralgorithmen bestimmt, denn Eigenschaften, die von keinem Sensor erfasst werden, müssen auch nicht simuliert werden. Allerdings darf hierbei die indirekte Wirkung verschiedener Einflüsse nicht außer acht gelassen werden. Als Beispiel sollen hier die absoluten Positionen von Objekten im Raum dienen, diese werden zwar selten direkt von Sensoren gemessen wird, haben aber über die relative Entfernung der eigenen Entfernungssensoren einen Einfluss. Was im einzelnen simuliert werden muß, hängt also von den vorhandenen und durch den Steueralgorithmus benutzten Sensoren und deren Eigenschaften ab. • Für Entfernungsensoren braucht man die Position und Orientierung aller Objekte im dreidimensionalen Raum. • Licht- beziehungsweise akkustische Sensoren erweitern dies noch auf die Oberflächen dieser Objekte falls deren Strukturgröße im Bereich der Wellenlänge liegt. (Für Licht braucht man zur Bestimmung der Reflektionseigenschaften die Unterscheidung zwischen rauhen oder glatten Oberflächen.) • Als weitere Gruppe kann man die verschiedenen Arten von Kameras betrachten, denn um diese zu simulieren muß auch das Aussehen von Objekten (Farbe, Textur), der Einfluß von Lichtquellen und der Schattenwurf von Objekten beachtet werden. Allerdings werden hier oft Vereinfachungen vorgenommen, da der für solche Simulationen anfallende Rechenaufwand auch so schon sehr hoch ist. Neben diesen statischen Eigenschaften gilt es weiterhin auch die dynamischen Eigenschaften zu beachten, da der Simulator auch den Zeitverlauf simulieren muß. Von den bereits genannten Objekteigenschaften ist normalerweise nur die Position und Orientierung veränderlich. Die Dynamik wird im wesentlichen durch Kräfte, Drehmomente, Masse, Trägheitsmomente und Reibung bestimmt. Einige diese Dinge kann man vernachlässigen oder durch Vereinfachungen beschreiben. Dies betrifft sehr oft die Reibung und zum Teil auch die Trägheitsmomente. Aber es gibt Anwendungsfälle, wo dies nicht möglich ist, Wenn zum Beispiel ein Roboter einen Anstieg hinauffährt, kann man die Reibung nicht außer acht lassen, denn sonst könnte dieser auch fast senkrechte Wände erklimmen. 6 2.2. Bisher verfügbare Systeme 2.1.2. Zeitablauf der Steuerung In der idealen Prozeßsteuerung gibt es keine Verzögerungen zwischen Eingabe der Messwerte und Ausgabe der Stellwerte, so das eigentlich das komplette System nur über Differentialgleichungssysteme zu simulieren wäre. Reale Systeme sind heutzutage allerdings meist rechnergesteuert, so das es zwangsläufig eine Verzögerung zwischen Ein- und Ausgabe gibt. Diese Verzögerung hängt ausschließlich vom abgearbeiteten Steuerungsalgorithmus ab und kann dementsprechend schwanken. Dies bedeutet unter anderem das es keinen festen Zeittakt für die Aktualisierung von Objekteigenschaften wie zum Beispiel Positionen gibt. In diesem Sinne muß der ideale Simulator zu beliebigen Zeitpunkten die Welt aktualisieren und damit den aktuellen Zustand der Sensoren liefern können. 2.1.3. Erweiterbarkeit Da man selten alle möglichen Anwendungsfälle von vorn herein abdecken kann, muß es möglich sein, das System um weitere Objekttypen oder Objekteigenschaften erweitern zu können. (Zum Beispiel neue Arten von Sensoren, die ihrerseits die Simulation einer bisher außer acht gelassenen Eigenschaft erfordern. Kamera⇒Farbe von Objekten) Das System muß dazu modular aufgebaut sein. Dadurch wird es möglich eigene Module hinzuzufügen und andererseits kann man existierende Module des Systems austauschen und verbessern. Der idealste Fall dieser Erweiterbarkeit besteht darin, das Erweiterungen des Systens auch ohne Neukompilierung oder andere tiefgreifende Änderungen desselben durchgeführt werden können. 2.1.4. Automatisierung Ein weiteres sehr wichtiges Feature des idealen Simulators ist die Fähigkeit die Simulation per Script oder durch andere Mittel extern zu steuern. Die sehr langen Laufzeiten von genetischen Algorithmen im Bereich von Stunden und Tagen, lassen es hier kaum zumutbar und sinnvoll erscheinen, die Überwachung und Steuerung einer solchen Simulation von Menschen übernehmen zu lassen. In diesen Fällen ist es unumgänglich, dies durch eine Software automatisiert tun zu lassen. Hierbei gibt es zwei Varianten. Entweder durch ein externes Interface wie zum Beispiel CORBA[13], wobei der Supervisoralgorithmus als externes Programm läuft oder das Simulationssystem ist selbst fähig Scripte in einer ausreichend flexiblen Sprache abzuarbeiten. In beiden Fällen ist es nicht unbedingt erforderlich die Visualisierung der Simulation durchzuführen, da diese ohnehin nur für den Menschen einen Wert hat. Das Simulationssystem kann dann im Hintergrundbetrieb arbeiten und so zum Beispiel auf UNIX-Rechnern, die nachts verfügbare Rechnerkapazität ausnutzen. 2.2. Bisher verfügbare Systeme 2.2.1. Webots Webots[52] ist ein kommerziell vertriebener Khepera-Simulator, der allerdings inzwischen auch die anderen Robotertypen (Alice, Koala) des K-Teams[28] simulieren kann. 7 2. Systemanforderungen Webots läuft auf Windows und verschiedenen UNIX-Varianten. Als Speicherformat wird ein angepasstes VRML-Format benutzt, dadurch ist es möglich jedes VRML-fähige Programm zum Erstellen der 3DUmgebung zu benutzen. Die Steuerungsalgorithmen werden als eigenständige Programme realisiert, welche über ein eigens entwickeltes Interface mittels IPC (Inter Process Communication) mit dem Simulator kommunizieren. Über diese Schnittstelle ist es sogar möglich grafische Interfaces aufzubauen. Der Simulator selbst simuliert nur die vordefinierten Roboter und deren Sensoren. Eine Erweiterung ist hier nicht möglich. Es gibt allerdings eine EAI (External Authoring Interface) genannte Schnittstelle über die sich alle Objekte manipulieren lassen einschließlich der Erzeugung neuer 3D-Objekte und ähnlicher Aktionen. Wenn man ein entsprechendes Steuermodul mit einer Scriptsprache ausstatten würde und das EAI dort verfügbar machte, hätte man hier bereits die beinahe perfekte Automatisierungslösung. Das noch bleibende Problem wäre die Benutzeroberfläche, denn als Hintergrundjob ist die überflüssig beziehungsweise lästig unter UNIX, denn auf welches Display sollte man die Ausgabe umlenken. Dieses Display müsste für unbeaufsichtigtes Betrieb gesperrt werden, und damit wäre der größte Vorteil der Hintergrundbetriebes auch schon wieder zunichte gemacht. (Immerhin würde ein Absturz oder Abschalten des Terminals wieder den Prozeß mit sich reißen.) 2.2.2. LV3D/Easybot Easybot[17] ist im wesentlichen ein Erweiterungsmodul für das 3D-Programm LightVision3D[30]. Es läuft nur unter Windows. Obwohl LV3D/Easybot selbst kostenlos für die Hochschule erhältlich ist, so fehlen doch die notwendigen Quelltexte um es selbst erweitern zu können. (Nur das Interface zu Easybot ist dokumentiert, das viel interessantere zu LV3D ist nicht öffentlich.) Als Speicherformat kommt ein eigenes Format zur Anwendung, zumindest können aber auch einige Fremdformate importiert werden. Dies ist besonders von Vorteil, da obwohl LV3D ein 3D-Programm ist, der Aufbau eigener Welten damit doch ziemlich gewöhnungsbedürftig ist. Easybot selbst simuliert nur die Sensoren. Die Berechnung, wo der Roboter sich nach Ausführung des Zeitabschnittes befindet, bleibt dem Steueralgorithmus überlassen1 . Ein weiteres Problem besteht darin, daß man alles in Weltkoordinaten berechnen muß und keinerlei Anhalt dafür hat, wie lang eigentlich die simulierten Zeitabschnitte sind. Weiterhin besteht von Easybot aus keinerlei Zugriff auf andere Objekte der 3D-Welt, so das hier nicht einmal ein Ansatz besteht irgendwelche Automatisierungs- oder Scriptfähigkeiten nachzurüsten (hätte man ein Interface zu LV3D direkt, liesse sich da wahrscheinlich etwas machen.) Die einzige Möglichkeit, die 3D-Welt zu beeinflussen, ist der direkte Eingriff durch den Benutzer. Weiterhin sollte erwähnt werden, das mehrere inkompatible Versionen des Programmes im Umlauf sind, die man leider nur daran erkennt, daß die gerade erstellten Easybot-Controller das gesamte Programm abschiessen können. 2.3. Eigenschaften des geplanten Systems Die wichtigste Eigenschaft ist die freie Verfügbarkeit der Quelltexte. Weiterhin soll auch alle Software, einschließlich derer zum Erstellen eigener Erweiterungsmodule, aus dem Bereich der freien Software 1 Ein Khepera wird eigentlich nur durch die Angabe der Geschwindigkeiten der beiden Räder gesteuert, das Simulationssystem sollte also die neue Position errechnen. 8 2.3. Eigenschaften des geplanten Systems kommen, beziehungsweise muß dort eine Alternative zu kommerzieller Software existieren. Als Beispiel soll unter Windows neben dem Microsoft Visual C++ Compiler auch der GNU C-Compiler[15] unterstützt werden. Das System soll portabel und auf so vielen Plattformen wie möglich nutzbar sein. AIX2 , Linux und Windows sind hier sozusagen Pflicht. Daneben wären noch FreeBSD und BeOS 5 PE[11] interessant, als weitere freie3 Betriebssysteme. Es soll vollständig modular aufgebaut sein. Das heißt jede größere abgrenzbare Funktionseinheit soll als eigenes Modul realisiert werden, andere Module müssen dann, die zu benutzenden Module explizit importieren. (Ähnlich wie die Klassen in Java, aber auf einer nicht ganz so tiefen Ebene.) Diese Module können ihre eigenen Konfigurationsdialoge mitbringen und diese werden dann entsprechend in die systemweiten Konfigurationsdialoge integriert. Das interne Datenmodell für die Objekte der simulierten Welt soll neben den üblichen 3D-Daten auch beliebige andere Daten (zum Beispiel von neuen Erweiterungsmodulen) aufnehmen und diese auch entsprechend laden und speichern können. Damit muß auch das externe Datenformat entsprechend flexibel sein. Da mir kein Datenformat bekannt ist, das von sich aus dermaßen flexibel ausgelegt ist, und auch kein existierendes Datenformat hier durch Nichtstandardelemente verunstaltet werden soll, wird hier eine Eigenentwicklung auf XML-Basis realisiert werden. Allerdings sollen bestimmte Strukturen existierender Dateiformate wie zum Beispiel VRML[50] übernommen werden, damit man diese Formate leicht in das eigene konvertieren kann. Weiteres Highlight soll die Integration einer Scriptsprache sein, von der aus Zugriff auf alle Objekte und Module im System besteht. Dadurch wird eine weitreichende Automatisierung durch externe Scripte möglich. Insbesondere ist der Betrieb durch alleinige Scriptsteuerung, ohne das eine Benutzeroberfläche notwendig ist, geplant. In der Simulationssteuerung selbst soll eine Ablaufsteuerung realisiert werden, die anders als existierende Steuerungen nicht auf feste Zeittakte festgelegt ist. Viel mehr soll der Zeittakt dynamisch durch die Robotersteuerungen vorgegeben werden. Außerdem ist vorgesehen, daß für die Simulation der Khepera-Roboter die Original-Quelltexte der Khepera-Steuerungen benutzt werden können. Diese werden dazu nach dem Compilieren, mit einem spezielles Stub-Objekt4 zu einer DLL gelinkt. Diese wird dann zur Laufzeit geladen und wie im echten Roboter die main-Funktion des Steueralgorithmus gestartet. Es gibt auch einige Dinge, die derzeit nicht geplant oder machbar sind. So ist das System softwaremäßig als Prototyp anzusehen, da die Zeit nicht reichte ein System solchen Umfangs vollständig zu programmieren und auszutesten. In dieser Beziehung wird voll auf die Modularität gesetzt, in dem für den Anfang nur einige Grundfunktionen der Module implementiert werden, um das Prinzip als solches zu demonstrieren. Es ist aber geplant das System schrittweise zu erweitern und zu perfektionieren. 2 Die Server an der HTW laufen unter AIX, und dies sind die einzigen Rechner, die sicher über mehrere Tage einen Hintergrundjob abarbeiten könnten. 3 BeOS 5 ist eigentlich nicht frei, aber die Personal Edition ist frei im Netz verfügbar. Außerdem hat BeOS einige interessante Eigenschaften, so das es sich wirklich lohnt einmal in dessen kernel-nahen Bereich (Threads) herumzustöbern. 4 Dieses Stub-Objekt enthält den Code um mit dem Simulator zu kommunizieren. Alle Funktionsaufrufe der eigentlichen Steuerung werden hier zum Simulator umgeleitet. 9 3. Grundstrukturen im System Nachdem die wesentlichen Anforderungen festgelegt wurden, sollen nun einige grundlegende Strukturen zum Erreichen dieser erläutert werden. Dabei ist zu beachten, daß diese Strukturen hier nur als abstraktes Modell der Funktionalität zu verstehen sind, die endgültige Realisierung könnte komplett anders aussehen, vorausgesetzt die Funktionalität bleibt erhalten. Weiterhin werden hier nur die zentralen Strukturen behandelt, die Funktionalität von zum Beispiel der 3D-Anzeige oder auch der Khepera-Simulation ist entsprechend dem Prinzip der Erweiterbarkeit durch Module auch als solche realisiert. Auf die vorhandenen Module, deren Zweck und Aufbau wird in einem späteren Kapitel eingegangen. 3.1. Interne Datenstruktur und Modularität Da in der Simulation hierarchisch aufgebaute 3D-Umgebungen verarbeitet werden sollen, kann eigentlich nur eine Baumstruktur zur Anwendung kommen. Dabei werden jeweils Teilelemente zu einer übergeordneten Instanz zusammengefaßt. So ist es zum Beispiel möglich die Position eines kompletten Khepera einschließlich Sensoren und Räder durch Manipulation einer einzelnen Transformationsmatrix zu verändern. Auf weitere Details soll hier nicht weiter eingegangen werden da diese Struktur von allen bekannten 3D-Toolkits (Java3D[26], MAM/VRS[32], ...) bereits benutzt wird. Für weitere allgemeine Informationen kann man sich deshalb auch derer Dokumentationen bedienen. root . . . . . Body Khepera Wheel, right Wheel, left . . . . . Abbildung 3.1.: Allgemeine Baumstruktur Neben dieser allgemeinen Struktur, gibt es noch einen zweiten Aspekt zu beachten. Die Nodes des Baums müssen neben den reinen Geometry-Daten auch noch beliebige Daten anderer Module speichern können. Der Idealfall wäre das eine Art Verzeichnis in dem man die Einträge über den Namen referenzieren kann. Durch Einfügen, Verändern und Löschen von Einträgen kann jedes Modul seine Daten in diesem Verzeichnis ablegen, einzige Voraussetzung ist hierbei ein eindeutiger Schlüssel, zum Beispiel der Modulname. 10 3.2. Abbildung auf das externe Datenformat Directory TransformationObject GeometryObject Objects_3D.geometry Objects_3D.appearance AppearanceObject Khepera.controller ControllerObject ...... ...... Node Objects_3D.transformation Abbildung 3.2.: Aufbau einer einzelnen Node 3.2. Abbildung auf das externe Datenformat Aufgrund der Flexibilität der internen Daten muß auch das externe Datenformat entsprechend flexibel sein. Es soll kein binäres Dateiformat zum Einsatz kommen, da hierbei wieder Probleme mit der PlattformUnabhängigkeit auftreten, einerseits durch unterschiedliche Anordnung der Bytes innerhalb eines Rechnerwortes andererseits durch die unterschiedliche Länge der Rechnerworte bei 32- und 64-BitComputern. Es bleibt also nur ein Text-basiertes Format übrig, allerdings ist mir derzeit kein entsprechend flexibles Format bekannt, so das eine Eigenentwicklung des Datenformats notwendig wird. Das einzige Datenformat mit dem man heute noch eigene Formate bauen und sich doch an einen Standard halten kann, ist XML[54]. Es gibt mehrere Gründe, die für XML sprechen. Da wäre die Standardisierung des XML selbst, die dazu führt, das es für nahezu jede Programmiersprache bereits fertige Parser gibt (zum Beispiel SAX[43].) Bei Benutzung eines solchen Parser muß man sich dann nicht mehr selbst um das Einlesen und Zerlegung der Daten kümmern, sondern bekommt diese schon entsprechend der Struktur zerlegt geliefert. Zweitens kann man in XML eigene Tags definieren, die zusammen mit der hierarchischen Struktur dazu führen, dass man zum Beispiel beim Auftreten eines bestimmten Tags auf eine komplett andere Menge von Tags umschalten kann (und bei der Rückkehr aus der untergeordnete Hierarchie wieder entsprechend zurück.) Dadurch ist es möglich unbekannte Strukturen unterhalb bestimmter Markierungstags abzulegen, für deren Interpretation dann das entsprechende Modul selbst zuständig ist. Wenn dieses Markierungstag den Modulnamen enthält, kann beim Laden dann das zuständige Modul gefunden, geladen und mit dem Laden der eigenen Daten betraut werden. Wie folgende DTD-Definition zeigt, wird es unterhalb des Toplevel (world) zwei Abschnitte geben. Im ersten (modules) werden die modulspezifischen Daten geladen, wobei wie bereits angedeutet, die Module selbst für das Laden der Daten innerhalb der module-Tags zuständig sind. (Prinzipiell können dort beliebige XML-Datenstrukturen abgelegt sein, was in der Definition durch das Wort ANY angezeigt wird.) Welches Modul zuständig ist, kann dem name-Attribut entnommen werden. 1 2 3 4 5 6 <!DOCTYPE <!ELEMENT <!ELEMENT <!ELEMENT <!ATTLIST <!ELEMENT world [ world (modules,node)> modules (module+)> module ANY> module name CDATA #REQUIRED > node ((data|node)*)> 11 3. Grundstrukturen im System 7 8 9 10 <!ATTLIST node name CDATA #REQUIRED > <!ELEMENT data ANY> <!ATTLIST data name CDATA #REQUIRED > ]> Der zweite Teil (node) speichert den kompletten Objektbaum, wobei jede Node durch ein node-Tag eingeleitet wird. Innerhalb des node-Elementes finden sich dann neben allen untergeordneten nodeElementen auch data-Elemente, die ihrerseits alle einer Node zugeordneten Daten speichern. Auch hier ist deren Speicherung allein vom zuständigen Modul abhängig. Eine Beispiel-Datei findet man im Anhang C auf Seite 87. 3.3. Signale und Callbacks Neben der Möglichkeit die Modul-Daten an die Nodes anzuhängen, braucht es zur Realisierung der Modularität aber auch noch der Kommunikation der Module untereinander. Im Allgemeinen muß man hierbei davon ausgehen, daß sich die Module gegenseitig nicht kennen1 . Ein Modul kann zwar seine Daten und Funktionen zur Verfügung stellen, aber es kann keine Funktionen eines später entwickelten, von ihm abhängigen Moduls aufrufen. Als Beispiel diene hier das Zeichnen benutzerdefinierter Objekte, die nicht im Objektbaum auftauchen. Dazu muß, wenn der Objektbaum gezeichnet wird, das eigene Modul aufgerufen werden, allerdings kann das Modul, welches den Objektbaum zeichnet, nichts von unserem Modul wissen, da es selbst zuerst da war. Dieses Problem kann nur durch den Einsatz von Signalen und Callbacks gelöst werden. (Beide sind im wesentlichen dieselbe Sache von zwei unterschiedlichen Seiten betrachtet.) Als sinnvoll erscheint hier die Definition von Callback-Objekten, die einerseits alle Callback-Funktionen registrieren und andererseits beim Auslösen eines Signals diese Funktionen aufrufen. Da oft mehrere Signale eines Moduls als zusammengehörig betrachtet werden können, sollte auch das Callback-Objekt mehrere Signale unterstützen. Zu diesem Zweck werden mehrere Slots im Objekt eingerichtet, die über ihren Namen referenziert werden können. func1 func2 func3 func4 registrierte Handlerfunktionen update reset changed func5 func6 ...... destroy ...... Callback Slots Abbildung 3.3.: Callback Die Module müssen nun ein solches Callback-Objekt anlegen, die notwendigen Slots darin anlegen und es öffentlich verfügbar machen. Wenn sich jetzt die Notwendigkeit einer Signalisierung ergibt (zum Beispiel Einfügen oder Löschen von Nodes im Objektbaum), dann braucht nur das entsprechende CallbackObjekt aufgerufen werden und dieses tut den Rest. Die am Signal interessierten Module müssen eine 1 12 Das Modul, welches ein anderes benutzt kennt dieses natürlich, aber die Umkehrung gilt normalerweise nicht. 3.4. Steuerung des Zeitablaufs Callback-Funktion bei dem zuständigen Callback-Objekt registrieren, und dies ist dann auch schon alles. Letztendlich ruft das signalisierende Modul Funktionen, in ihm gänzlich unbekannten Modulen, auf. 3.4. Steuerung des Zeitablaufs Nachdem nun die eher statischen Strukturen bekannt sind, muß noch die grundlegende Arbeitsweise der Ablaufsteuerung der Robotersteuerungen festgelegt werden. 3.4.1. Prinzipielle Möglichkeiten Nachfolgend werden mehrere Möglichkeiten zur Steuerung des Zeitablaufs betrachtet. Dabei sind folgende Faktoren besonders interessant: • die Flexibilität der Zeiteinteilung • die Bearbeitbarkeit mehrerer Algorithmen mit unterschiedlichen Anforderungen • das (Echtzeit-)Verhalten, wenn der zu bearbeitende Algorithmus weniger Zeit als vorhanden benötigt oder wenn er mehr braucht Fester Zeittakt Bei diesem Verfahren wird eine Routine des Simulationsalgorithmus regelmäßig in bestimmten Zeitabständen aufgerufen. Dies ist zum Beispiel die übliche Vorgehensweise bei Computerspielen, wobei der Zeittakt durch die Bildwiederholfrequenz der Grafikkarte bestimmt wird. Diese Vorgehensweise hat folgende Eigenschaften: • Wird der Zeittakt von außen festgelegt, muß der Algorithmus diesen abfragen können, um sich anzupassen. So ist aber keinesfalls immer der optimale Zeittakt, den der Algorithmus benötigt, garantiert. Legt der Algorithmus den Zeittakt fest, dann gibt es Probleme wenn mehrere Algorithmen unterschiedliche Zeittakte möchten. Abhilfe wäre hier das Ausrechnen der größten gemeinsamen Nenners bezüglich des Zeittaktes und Aufruf der Algorithmen aller n Takte. Richtig kompliziert wird es, wenn ein Algorithmus interne Verzögerungen braucht, diese müssten dann auch eingerechnet werden. Das bedeutet allerdings auch, das alle Zeiteinheiten vorher bekannt sein müssen. • Wenn solch ein Algorithmus weniger Zeit braucht als notwendig, gibt es keinerlei Probleme, es wird dann eine Pause bis zur nächsten Aktivierung eingelegt. Falls er mehr Zeit braucht, dann gibt es zwei Varianten: Entweder werden verlorene Zeittakte weggelassen (Beispiel Computerspiele, wenn der Rechner es nicht schafft die Szene neu zu zeichnen bis zum Bildwechsel, wird nach Abschluss der Berechnungen trotzdem bis zum nächsten Bildwechsel gewartet.2 ) Andererseits kann man auch sofort den nächsten Zeittakt unmittelbar hinterher schieben, um den Zeitverlust gering zu halten. Das gesamte System wird entsprechend langsamer, es sei denn der 2 Es reduziert sich also die Bildfrequenz auf die Hälfte gleichzeitig wird aber intern der Zeittakt entsprechend verdoppelt um trotzdem Echtzeitverhalten zu erreichen. 13 3. Grundstrukturen im System Algorithmus kann mit längeren Zeittakten arbeiten, dann sollten entsprechende Maßnahmen vorgenommen werden, um diese zu setzen.3 Kein Zeittakt, Eventgesteuerter Ablauf Bei diesen Verfahren gibt es keinen Zeittakt, stattdessen bestimmt der Algorithmus wieviel simulierte Zeit vergangen ist. Dazu ruft er bestimmte Funktionen der Ablaufsteuerung auf, um diese zu informieren, welche Zeitpunkte der Algorithmus aktuell erreicht hat oder um irgendwelche anderen Sonderfälle anzuzeigen. Insgesamt kann man folgende Eigenschaften erkennen: • Da der Algorithmus selbst seine Zeiteinteilung verwaltet, sind hier keinerlei Einschränkungen diesbezüglich zu erkennen. • Wenn mehrere Algorithmen abzuarbeiten sind, können diese ihre eigenen Zeitabläufe festlegen. Die Simulationssteuerung muß dann dafür sorgen, das Algorithmen in der korrekten Reihenfolge ablaufen, bezüglich ihrer Ein- und Ausgabeaktionen in der simulierten Umgebung. Dazu ist es notwendig die Algorithmen bei einer Zeitmeldung anhalten zu können, um andere laufende Algorithmen auf mindestens dieselben Stand der Simulationszeit zu bringen, dies läßt sich elegant durch Laufen lassen der Algorithmen in separaten Threads erreichen (Blockieren durch übliche Synchronisationsmechanismen). Allerdings ist hier zu beachten, das durch die Forderung nach korrekter Reihenfolge der Ereignisse die Threads zwangsläufig serialisiert werden. 3.4.2. Beschreibung der gewählten Variante Alle mir bisher bekannten Simulatoren benutzen ein festes Zeitraster. Wie aber aus den vorangegangenen Ausführungen ersichtlich geworden sein sollte, ist die eventgesteuerte Variante wesentlich flexibler, wenn auch schwieriger zu realisieren. Ein Beispiel soll hier einmal kurz demonstrieren, wie ein entsprechendes Programm und dessen Ablauf aussehen würden. Die eigentliche Steuerung sei als externe Bibliothek vorhanden. Während man bei festem Zeitraster, den Algorithmus als Funktion schreiben muß, kann man hier ein nahezu eigenständiges Programm schreiben. 1 2 3 4 5 6 7 8 9 10 11 12 3 14 void x(void* data) { int i; printf("Start %s",data); while(1) { /* der zu simulierende Algorithmus */ for(i=0;i<3;++i) /* 3 Durchl"aufe ohne Interaktion mit der Umgebung */ { /* get_simtime() = Zeitstand der Simulationssteuerung */ /* get_time() = Zeitstand des Algorithmus */ Wenn Echtzeitverhalten gefordert ist, könnte der Algorithmus auch die Zeit direkt messen, die seit der letzten Aktivierung vergangen ist und dieses als aktuellen Zeittakt für die internen Berechnungen verwenden. Praktisches Beispiel siehe [6]. 3.4. Steuerung des Zeitablaufs 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 printf("%f %f: %s\n",get_simtime(),get_time(),data); /* die letzte Aktion soll 2 Sekunden verbraucht haben */ checkpoint(2.0); } request_io(); /* Anforderung der Interaktion, sorgt daf"ur, das nach diesem Befehl alle laufenden Aktionen mindestens denselben Zeitstand wie diese haben*/ /* hier w"are irgendeine Interaktion mit der Umgebung */ } } void main(void) { init(); /* abzuarbeitende Algorithmen registrieren */ /* Parameter: (function,data,starttime,destructor_f"ur_data) */ register_function(x,"TEST I",0.0,NULL); register_function(x,"TEST II",1.0,NULL); /* nach 10 simulierten Sekunden anhalten */ set_simtime_to_stop(10.0); run(); /* Simulation ausf"uhren */ } Die Funktion x soll den auszuführenden Algorithmus darstellen. Wie man sieht, muß der Algorithmus selbst seine Zeit fortschreiben, indem er seine verbrauchte Zeit mittels checkpoint(zeitdauer) der Simulationssteuerung bekannt gibt. Weiterhin muß jede Interaktion mit der Umwelt angemeldet werden (request_io()) damit die anderen laufenden Algorithmen wenn nötig aufholen können. (Gleichzeitig würde man auch die Bewegung von Objekten bis zu diesem Zeitpunkt realisieren.) Es existiert noch eine zweite Variante synchronize(), die nur der Synchronisation zwischen verschiedenen Algorithmen dient, wenn diese Daten untereinander austauschen müssen. (Dabei bräuchte man nicht unbedingt die Umwelt bezüglich der Objektbewegungen oder ähnlichem zu aktualisieren.) Wenn man dieses Beispiel ausführt, sollte sich folgendes Resultat einstellen: (Abbildung 3.4 zeigt die zugehörige grafische Darstellung.) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Start TEST I 0.000000 0.000000: TEST I 0.000000 2.000000: TEST I 0.000000 4.000000: TEST I Start TEST II 1.000000 1.000000: TEST II 1.000000 3.000000: TEST II 1.000000 5.000000: TEST II 6.000000 6.000000: TEST I 6.000000 8.000000: TEST I 6.000000 10.000000: TEST I 7.000000 7.000000: TEST II 7.000000 9.000000: TEST II 7.000000 11.000000: TEST II 15 3. Grundstrukturen im System TEST I TEST II 1 2 3 Checkpoint IO-Request 4 5 6 7 8 9 10 11 12 13 Zeit normaler Funktionsablauf Wechsel durch Simulationssteuerung Abbildung 3.4.: Zeitablauf bei eventgesteuerter Simulation In der ersten Spalte der Tabelle steht die allgemeine Zeit der Ablaufsteuerung, in der zweiten die des laufenden Algorithmus und die dritte bezeichnet den laufenden Algorithmus. An der erste Spalte sieht man, daß eigentlich nur 4 verschiedene Zeitpunkte für die Außenwelt sichtbar werden. Dies sind jeweils die Zeitpunkte, wenn einer der Algorithmen eine Interaktion mit der Außenwelt plant. (Im Bild sind diese durch die senkrechten Linien markiert.) Weiterhin ist zu erkennen das TEST I erstmal losläuft und der allgemeinen Zeit voraus ist, dies ist aber völlig egal, solange er noch keine Interaktion mit der Umwelt hatte. Zum Zeitpunkt 6.0 ist dann eine solche geplant. An dieser Stelle muß erstmal TEST II gestartet werden damit keine seiner Einwirkungen auf die Umwelt verloren geht. Dessen erste Interaktion wird für den Zeitpunkt 7.0 angefordert. An dieser Stelle haben alle laufenden Algorithmen den Zeitpunkt 6.0 erreicht oder überschritten, so das die allgemeine Simulationszeit entsprechend gesetzt werden kann. Nun darf TEST I weiterlaufen und seine angekündigten Interaktionen durchführen. (Alle Einflüsse auf die Umwelt bis zu diesem Zeitpunkt sind bekannt, dadurch kann diese auch für diesen Zeitpunkt vollständig berechnet werden.) TEST I läuft bis zum Zeitpunkt 12.0, wo er erneut eine Interaktion plant. Die allgemeine Simulationszeit wird jetzt auf 7.0 gesetzt. Dann wird TEST II aktiviert, um seine Interaktion auszuführen und fortzufahren. Nächster Interaktionszeitpunkt wäre 13.0. Die allgemeine Simulationszeit würde jetzt auf den nächsten Interaktionszeitpunkt (12.0) weitergesetzt, dabei wird allerdings die vorher gesetzte Grenze überschritten und die Simulation angehalten. 3.5. Benutzung mehrerer Threads Im Rahmen des Systems gibt es mehrere Einsatzmöglichkeiten für Threads. Ein Einsatzgebiet ist der eben erwähnte Scheduleralgorithmus. Da hierbei Funktionen jederzeit angehalten und später wieder fortgesetzt werden müssen, kommen hier zwangsläufig Threads zum Einsatz. Da diese aber serialisiert abgearbeitet werden, bringt das wenig Vorteile, wenn man zum Beispiel einen Multiprozessor-Computer sein eigen nennt. Ein anderes Einsatzgebiet besteht in der Benutzung verschiedener Threads für Benutzeroberfläche und die Simulation. Da, wenn die Simulation in realer Zeit ausgeführt werden soll, der Scheduler immer wieder kurz anhalten muß, ergibt sich hier eine Vereinfachung, wenn diese Pausen einfach durch usleep() im eigenen Thread realisiert werden können. Anderenfalls müßte der Scheduler über das Grafik-Toolkit der Benutzeroberfläche synchronisiert werden, was zu einer zu starken gegenseitigen Verflechtung führt. Neben diesem Vorteil von der Programmierung her, ergibt sich allerdings noch ein anderer Effekt. Wenn 16 3.5. Benutzung mehrerer Threads die Visualisierung relativ viel Zeit benötigt, wird sie vom Betriebssystem als langlaufender Thread eingestuft (weil er seine Zeitscheibe immer voll ausnutzt.) Die Simulationsthreads mit ihren vielen kurzen Aktivierungen werden aber eher als interaktiver Prozess eingeordnet und bekommen damit gewöhnlich höhere Priorität. Dies hat zur Folge, daß im Zweifelsfalle eher die Geschwindigkeit der Visualisierung abnimmt als die der Simulation. Ein gutes Beispiel findet sich hierzu in [7]4 . Eine weitere Möglichkeit besteht darin die Bearbeitung der Benutzeroberfläche und der Visualisierung in zwei Threads aufzuspalten. Wenn die Visualisierung durch rechenintensive 3D-Ausgaben stark beansprucht ist, würde die eigentliche Benutzeroberfläche noch genauso schnell reagieren wie üblich. Dies verdankt man wieder dem Betriebssystem, welches die auf Ereignisse wartende Benutzeroberfläche von der Priorität höher einstuft. Leider stellt sich aber schnell heraus, daß diese Variante unter UNIX kaum nutzbar ist, da beide Threads auf die Xlib-Bibliothek zugreifen, diese aber selten thread-safe ausgelegt ist. Unter Windows sieht die Sache besser aus, wie das oben bereits erwähnte Beispiel demonstriert. Aber auch hier findet man schnell heraus, daß die Treiber der Hardware-OpenGL-Beschleunigung nicht immer thread-safe programmiert sind. Zusammenfassend ist also festzustellen, daß die Aufteilung von Simulation und restlichen Programm auf verschiedene Threads gewisse Vorteile, hauptsächlich im Bereich der sauberen Trennung, mit sich bringt. Die Benutzung verschiedener Threads im Bereich der Benutzeroberfläche und Visualisierung schafft hingegen neue Probleme auf die man getrost verzichten kann. 4 Dort läuft die komplette Anzeige als separater Thread, durch Aktivieren der Anzeige der Reglerzustände geht die Bildfrequenz von ungefähr 20 Bilder/s bis auf 2 Bilder/s oder weniger herunter, dies ist abhängig vom Rechner, während die Simulation unbeeindruckt weiterläuft. (Diese Zustandsanzeige ist nicht sonderlich elegant programmiert, sondern benutzt Standard-Windows-Elemente und wird deshalb so langsam.) 17 4. Auswahl der Software Mit den bisher festgelegten Systemmerkmalen kann man nun eine Auswahl an Software festlegen, mit deren Hilfe das System letztendlich implementiert werden soll. Neben der Möglichkeit alles komplett neu zu schreiben, existiert heutzutage auch eine breite Auswahl an frei verfügbaren Softwarepaketen, so daß man sich hier viel Arbeit sparen kann. Das Problem besteht hier eher darin, das Brauchbare von dem Unbrauchbaren zu unterscheiden, und wenn man mehrere Möglichkeiten hat, sich zwischen diesen zu entscheiden. 4.1. Grundvoraussetzungen Damit ein entsprechendes Paket hier in Frage kommt, sollte es einige grundlegende Voraussetzungen erfüllen. • Es muß frei verfügbar für jederman sein, also wenn möglich unter der GPL, LGPL, BSD-Lizenz oder ähnlichem freigegeben sein. Damit ist sichergestellt, das jeder der unser Programm hat, dieses verändern und neu übersetzen kann. (Sonst wäre erweiterbar auf den ursprünglichen Hersteller/Programmierer beschränkt, und damit aus Anwendersicht nicht erweiterbar.) • Es sollte auf möglichst vielen Betriebssystemumgebungen lauffähig sein, oder zumindest muß eine entsprechende Variante für Abweichler existieren. Aus Programmierersicht muß es allerdings identisch sein. (Sonst wäre unser Programm nicht mehr uneingeschränkt portabel.) Als Betriebssysteme sind mindestens folgende zu berücksichtigen: – – – – Linux, das freie System überhaupt. Window 98/NT4, für alle die die kein Unix mögen. AIX, weil unsere Rechenzentrumsrechner damit laufen.1 FreeBSD, das andere freie System (Besonders außerhalb Europas weitverbreitet.) • Der eigentliche Haupteinsatzzweck des Pakets sollte unserem möglichst genau entsprechen und unsere Anforderungen voll erfüllen können. Wenn es das nicht kann, muß es entsprechend erweiterbar sein. • Es sollte in C geschrieben sein, C++ ist hier zweite Wahl, da der Entwicklungsstand der C++Compiler sehr unterschiedlich sein ist (Templates, Parameter in Templates und Exceptions.) Das gilt zum Beispiel insbesondere auch für den gcc in seinen verschiedenen 2.xx-Versionen. Keiner sollte gezwungen sein, einen neuen Compiler installieren zu müssen, nur weil das Programm nicht kompiliert. 1 Es gibt mindestens eine Maschine im Rechenzentrum mit mehreren Prozessoren, und damit die Möglichkeit, die eigene Threadprogrammierung unter verschärften Bedingungen zu testen. Außerdem ist das der Platz für langlaufende Jobs, LinuxRechner in der HTW sind normalerweise allgemein zugänglich und damit anfällig für „zufällige“ Restarts. 18 4.2. 3D-Modell und Visualisierung • Wenn ein solches Paket zusätzliche Dateien installiert braucht bzw. selbst installiert werden muß, sind folgende Varianten für Installationsorte vorzuziehen: (in absteigender Reihenfolge der Akzeptanz sortiert) – Aktuelles Verzeichnis. – Beliebiges Verzeichnis, welches durch Umgebungsvariablen dem Programm bekannt gemacht wird. – Beliebiges Verzeichnis, mit Registry-Einträgen unter Windows (Dies kann durch einen normalen Nutzer oft noch selbst installiert werden.) – Systemverzeichnis unter Windows, oft sind die Nutzer Administrator auf der eigenen Maschine. – Systemverzeichnis unter UNIX (/usr/local/* oder ähnliches), dafür braucht man Administrationsrechte, was bei unseren Rechenzentrumsrechnern zum Beispiel nicht mehr ohne weiteres selbst geht. (Es gibt heutzutage wenige Programmpakete, die wirklich so restriktiv sind.) 4.2. 3D-Modell und Visualisierung Da die 3D-Visualisierung sehr rechenintensiv ist, sollte ein System zum Einsatz kommen, das vorhandene Hardwareunterstützung auch ausnutzen kann. Als Basis kommen daher nur OpenGL[35][34] und DirectX in Frage2 , wobei DirectX gleich wieder gestrichen werden kann, weil es nur auf Windows läuft. Also muß es ein Paket sein, welches auf OpenGL basiert. (Wenn ein System von sich kein OpenGL bietet, kann man dies mittels der Mesa-Bibliothek[46] nachrüsten.) Weiterhin ist es unumgänglich, daß diese Software ihr 3D-Modell dynamisch verändern kann und Kollisionen erkennt. Alternativ zur Kollisionserkennung ist es auch möglich die Mesh-Daten zu verarbeiten, zum Beispiel mit SOLID[3]. Dazu braucht man allerdings entsprechenden Zugriff auf diese Daten. Als dritte Voraussetzung muß man andere Daten in dieses Modell integrieren können. Es existieren einige freie 3D-Softwarepakete. Als Beispiel seien hier gleem[21], MAM/VRS[32] und VTK[47] genannt, die alle eher fertige Systeme darstellen. Das Hauptproblem ist, daß sie entweder primär auf Visualisierung ausgelegt sind oder nicht flexibel genug hinsichtlich der Zusatzdaten bzw. der Kollisionserkennung sind. Bisweilen ist auch das Softwarepaket dermaßen umfangreich, das es am Ende der größte Bestandteil des Simulator sein würde, ohne das die meisten Features jemals genutzt würden. Weiterhin wäre hier noch Java3D[26] interessant, immerhin laufen Java-Programme überall ohne Neuübersetzung. Aber auch hier gibt es einige Nachteile, erstens ist Java3D nicht auf allen gewünschten Plattformen verfügbar und zweitens wird für die Integration der normalen Khepera-Steuerprogramme auf jedenfall ein Interface in C notwendig, und damit wäre die Plattformunabhängigkeit dahin. Ein weiteres sehr interessantes Packet stellt hier Crystal Space[14] dar. Dieses System ist ursprünglich für Spiele entwickelt wurden. Aber der Unterschied zwischen 3D-Spiel und 3D-Simulation ist gar nicht so groß wie man vielleicht meint, die Frage ist nur wer steuert, Mensch oder Roboter-Steuerprogramm. Als besonders interessante Eigenschaften seien hier folgende genannt, es ist auf fast jeder beliebigen Hardware die optimale Geschwindigkeit der 3D-Ausgabe zu erreichen3 , eine Physik/Dynamik-Simulation ist in Entwicklung und es existiert mit Python bereits eine Scriptanbindung. Allerdings muß man auch sagen, durch die Ausrichtung auf Spiele ist das System sehr groß und enthält viele Bestandteile, die für 2 Ich habe noch nie von irgendwelcher Hardwareunterstützung für Phigs/PEX gehört, läuft es überhaupt unter Windows ? PC’s, wenn keine Hardwareunterstützung vorhanden ist, durch Softwarerendering mittels MMX-Assemblerroutinen. 3 Auf 19 4. Auswahl der Software einen Simulator nicht unbedingt notwendig sind. Es könnte aber sein, das eventuell eine spätere Version des Simulators einmal hierauf aufbaut. Da keines der oben genannten Softwarepakete hier optimal erscheint4 , läuft das erstmal darauf hinaus, daß ein eigenes Modell aufgebaut wird und dieses dann mittels OpenGL visualisiert wird. Durch die eigene Implementation lassen sich hier natürlich die gewünschten Grundstrukturen 1:1 für die Programmierung übernehmen. Vorläufig sollen allerdings nur ein paar grundlegende 3D-Objekte realisiert werden, ungefähr im Umfang dessen was VRML[51][50] bietet. (Der Hauptzweck des Systems ist die Simulation, nicht die Erstellung absolut realistischer Welten.) Allerdings ist eine entsprechende Erweiterbarkeit geplant, in der Art, das jedes neue Objekt sich selbst mittels OpenGL zeichnen kann und andererseits die Kollisionstests auch selbst organisiert. Die Arbeit mit OpenGL soll hierbei unter extensiver Nutzung der Displaylisten erfolgen, insbesondere sollen verschieden OpenGL-Kontexte gemeinsame Displaylisten benutzen. 4.3. Grafische Benutzeroberfläche Nach der Entscheidung für OpenGL, kommen für die Benutzeroberfläche nur noch solche Toolkits in Frage, die auch etwas mit OpenGL anfangen können, beziehungsweise die durch Erweiterungen dazu fähig werden. Als Basis für die Benutzeroberfläche kommen mehrere Toolkits in Frage, die jetzt erstmal aussortiert werden sollen. (Einen größeren Überblick erhält man unter [45].) • MFC, Microsoft Foundation Classes, läuft nur unter Windows. / • XVT DSC[55], läuft unter AIX, Linux und Windows. Ist nicht frei (kommerziell.) Keine OpenGLAnbindung bekannt. / • Motif/Lesstif[33][29], läuft unter Unix. (Motif ist kommerziell. Lesstif ist die freie Variante und läuft auch auf nicht Unix-Systemen mit X-Server.) OpenGL? / • Qt[42], läuft unter Windows und Unix. Aber die Windows-Variante ist nicht frei. OpenGL-Widget ist vorhanden. / • GTK+[22], läuft unter Unix und Windows. Freier Quellcode. Mehrere Interface-Builder verfügbar. Es kann auch von andere Sprachen als C benutzt werden. OpenGL-Widget (GtkGlArea[9]) vorhanden. In Standard-Linux und FreeBSD-Installationen oft schon vorhanden. , • Tcl/Tk[44], läuft unter Unix und Windows. Freier Quellcode. Auch von anderen Sprachen als ansprechbar. Stark gekoppelt an Tcl. Keine komplexeren Elemente wie Trees, Notebooks u.ä. Kann nachgerüstet werden entweder durch Tix[48] oder als Tcl-Code. OpenGL-Widget (Togl[49]) vorhanden, aber unterstützt keine gemeinsamen Displaylisten. , • wxWindows[53] läuft unter Windows und Unix. Unter Unix wird Motif oder GTK benutzt, unter Windows MFC. MFC-ähnliche Programmierung in C++. OpenGL-Widget integriert. , Also bleiben eigentlich nur zwei Varianten übrig, GTK+ und wxWindows. Für beide müssen unter Unix Shared-Libraries installiert werden, wenn man keine statisch gelinkten Programme benutzen will. Bei 4 20 Das Hauptproblem ist hier oft die fehlende Möglichkeit auf bestimmte Aktionen oder Ereignisse reagieren zu können. Das Beispiel-Module in Kapitel 12 ist zum Beispiel darauf angewiesen jede Positionsänderung sofort gemeldet zu bekommen. 4.4. Scriptsprache GTK+ ist eher davon auszugehen, das es schon installiert ist (allein schon, weil Gnome so weit verbreitet ist.) Bei der GTK-Windows-Variante reicht es, die DLLs in das entsprechende Programmverzeichnis zu kopieren. So das hier GTK+ zum Einsatz kommen wird. 4.4. Scriptsprache Da das System scriptsteuerbar sein soll, muß auch hier noch eine entsprechende Auswahl getroffen werden. Ein paar besondere Aspekte sollen hier zusätzlich zur Entscheidung herangezogen werden. Der erste wäre die Möglichkeit GTK von der Scriptsprache aus zu benutzen, das würde es ermöglichen die gesamte Benutzeroberfläche in der Scriptsprache zu programmieren. Als zweites, sollte die Scriptsprache das Einlesen von XML unterstützen und als letztes sollte die Sprache ein Modul- und Objektkonzept kennen. Obwohl viele andere Scriptsprachen existieren, scheinen hier Perl, Python und Guile/Scheme die besten Voraussetzungen zu haben, diese Punkte optimal zu erfüllen. Außerdem sind diese Sprache weitverbreitet, so daß der zukünftige Benutzer die Sprache entweder schon kennt oder aber ausreichend Literatur zur Verfügung steht, um sie zu erlernen. • Perl[36] ist eine Sprache, die an die Shellprogrammierung angelehnt ist. Dort ist wohl auch eher das Haupteinsatzgebiet zu sehen. Außerdem sind viele Dinge erst nachträglich hinzugekommen, die bei neueren Programmiersprachen schon von Anfang an dabei und damit entsprechend besser integriert sind. • Python[40] ist eher als eine normale Programmiersprache zu betrachten (eine Art Kreuzung zwischen Java, Basic und Lisp.) Ein Vorteil ist sein Modulkonzept, so kann man Module als PythonScript schreiben oder man realisiert sie als DLL, das benutzende Programm bemerkt hierbei keinen Unterschied. Man könnte also einen Prototyp in Python schreiben und später nach C konvertieren, um die Geschwindigkeit zu erhöhen, ohne das restliche Programm zu ändern. Weiterhin kann man hier sehr einfach seine eigenen Objekttypen auf C-Ebene definieren, was viele Dinge erheblich vereinfacht. (Übrigens existiert auch eine Python-Anbindung an wxWindows.) • Guile[25] ist ein Scheme-Dialekt. Das dürfte für die meisten schon ein Ablehnungsgrund sein (wegen der Unmengen von Klammern.) Eine zweites Problem ist die Qualität der GTKAnbindung[24] insbesondere des OpenGL-Widgets[23], die derzeit noch nicht richtig funktioniert. Allerdings gibt es auch einige interessante Eigenschaften, die man sich manchmal auch in anderen Sprachen wünscht, da sind einerseits die Lisp-Features wie zum Beispiel das lambda-Konstrukt zum Erzeugen anonymer Funktionen, aber vielleicht noch interessanter für die Erweiterung in C ist der eingebaute Garbage Collector. Während andere Sprachen ständig Referenzen zählen und bei gegenseitiger Referenzierung schnell Probleme bekommen, existiert dieses Problem hier einfach nicht. Die einzige Aktion, die ein Objekt unterstützen muß, ist das Belegt-Melden seine Unterobjekte. Aufgrund der Bevorzugung einer „richtigen“ Programmiersprache fällt Perl hier schon mal heraus, da andererseits Guile noch einige Probleme mit der Oberflächenprogrammierung hat, fällt die Entscheidung hier zugunsten von Python aus. Es gibt noch einige andere Gründe die für Python sprechen, erstens wird es bereits von einigen anderen interessanten Paketen benutzt, es gibt eine ausgezeichnete Dokumentation[38][39] und es ist relativ schnell zu erlernen. 21 4. Auswahl der Software Zum Abschluß noch ein paar Worte zu Java. Wenn nicht die C-Schnittstelle notwendig wäre und Java3D auf mehr Plattformen laufen würde, hätte man den kompletten Simulator auf Java basieren lassen können. Die Komponenten wären hierbei folgende: • Java als Basisprogrammiersprache, • AWT/Swing als Benutzeroberfläche, • Java3D[26] zur 3D-Visualisierung und • JPython[27] als Scriptsprache. (JPython ist eine vollständig auf Java basierende Version von Python.) 22 5. Python Da ein großer Teil des Simulators in Python realisiert wurde, soll zuerst eine kurze Übersicht über Python und die benutzten Erweiterungsmodule folgen. Weitere Informationen zu den einzelnen Paketen kann man deren Dokumentationen entnehmen. 5.1. Die Programmiersprache Python 5.1.1. Eine kurze Sprachvorstellung Python ist eigentlich nicht so recht mit anderen Programmier- oder Scriptsprachen vergleichbar. Am ehestens kann man es noch als eine Mischung von Java, Basic und Lisp beschreiben wenn man es zum ersten mal sieht. Darum soll jetzt eine sicherlich nicht komplette Liste der wichtigsten Sprachfeatures etwas mehr Einblick geben. • Python ist eine interpretative Sprache. Beim Laden kommt allerdings ein Bytecode-Compiler zum Einsatz, der übersetzte Code wird als Datei abgelegt und beim nächsten Mal direkt geladen, vorausgesetzt die Orignaldatei hat sich nicht geändert. In Java enspräche das einer Java Virtual Maschine mit eingebautem Java Compiler. • Es gibt ein Modulkonzept mit hierarchischer Struktur ähnlich dessen das Java für seine Klassen benutzt. • Module können beliebigen Code enthalten, einschließlich von Funktions- und Klassendefinitionen. Der Code wird bereits beim Laden ausgeführt. Die Definitionen sind in diesem Sinne Funktionen, die aufrufbare Objekte in das Symbolverzeichnis des Moduls eintragen. (Dies ist damit ähnlich wie in Lisp.) • Wie bereits angedeutet werden Klassen unterstützt, einschließlich Vererbung und aller anderen üblichen Features. Was fehlt sind echte private Variablen, aber man kann durch Voranstellen zweier Unterstriche die Namen nach außen praktisch unsichtbar machen, da Python dann noch den Klassennamen davorsetzt. • Es werden Exceptions unterstützt. • In Python müssen Variablen nicht deklariert werden. Sie werden angelegt, wenn zum ersten Mal irgendwelche Daten an einen noch nicht belegten Namen zugewiesen werden. (Versuche eine nicht existierende Variable zu lesen, erzeugen eine Exception.) • Man kann existierende Variablen wieder vernichten, oder besser gesagt den Namen aus dem Symbolverzeichnis löschen. 23 5. Python • Variablen sind nicht typgebunden. Man kann alle Arten von Daten zuweisen. • Es gibt leistungsfähige Datentypen wie zum Beispiel Listen, Tupel und Verzeichnisse. • Es werden viele von Lisp bekannte Operationen auf Listen unterstützt. • Die Verzeichnisse werden intern intensiv benutzt, zum Beispiel sind die Module eigentlich Verzeichnisse von Symbolen und zugeordneten Objekten (Daten und Funktionen). Ähnlich sieht das mit den lokalen Variablen aus. Auch Klassen sind so gesehen, ein Verzeichnis. • Es gibt eine Art Funktionspointer, eigentlich eine Referenz auf ein aufrufbares Objekt. Für Methoden von Klassen gibt es sowohl gebundene wie auch ungebundene Funktionspointer. • Man kann wie in Lisp mittels eines Lambda-Konstrukts unbenannte Funktionen erzeugen. • Python unterstützt Threads und ist intern entsprechend ausgelegt. • Und was vielleicht als erstes auffällt, Python benutzt keine expliziten Strukturierungselemente wie geschweifte Klammern oder BEGIN/END, die komplette Strukturierung erfolgt durch Einrückung. Dadurch ist man gezwungen ordentlich eingerückte gut lesbare Programme zu schreiben. • Jedes Modul und jede Klassen- oder Funktionsdefinition kann als erstes Element einen sogenannten Dokumentationsstring enthalten. Dadurch ist es möglich, mittels spezieller Browser, die Module als Online-Dokumentation zu benutzen. Eine gute Einleitung in die Programmierung mit Python erhält man durch das Python Tutorial[41], welches auch als deutsche Übersetzung[39] verfügbar ist. 5.1.2. Anwendungsbereiche Von der Zielsetzung geht die Sprache eher in die Richtung Perl anstatt Java, also Scriptprogrammierung und weniger Applets und Oberflächen. Dies wird auch durch die umfangreiche Ausstattung an Erweiterungsmodulen mit denen Python daherkommt schnell klar. Dazu gehören zum Beispiel eine stattliche Sammlung von Internetprotokollen (http, pop3, imap4, ftp, ...) und Dateiformaten (HTML, XML, Mime, ... .) Allerdings gibt es auch eine Anbindung an Tcl/Tk, die allerdings ein fertig installiertes Tcl/Tk voraussetzt, so daß man auch Oberflächen ohne viel Aufwand programmieren kann. (Die Windows-Version wird standardmäßig mit Tcl/Tk ausgeliefert.) Neben diesen bereits vorhandenen Erweiterungsmodulen kann man jederzeit eigene erstellen, und das nicht nur in Python, sondern auch in C oder C++. Auch dazu findet man auf der Python-Webseite eine gute Dokumentation. 5.1.3. Entwicklungsstand Seit April 1999 ist die Version 1.5.2 im Umlauf, in dieser Version wurden relativ viele Dinge gegenüber der Vorversion geändert, so daß man keine ältere Version benutzen sollte. Das Simulationssystem läßt 24 5.2. Erweiterungsmodule sich mit der alten Version gar nicht erst installieren, weil bereits zur Installation die Version 1.5.2 benötigt wird. (siehe nächsten Abschnitt) Im Sommer oder Herbst 2000 soll die Nachfolgeversion 1.6 erscheinen. Ein paar kurze Tests mit der verfügbaren Beta-Version 1.6b1 haben gezeigt, daß das Simulationsprogramm auch dort ohne Probleme läuft. 5.2. Erweiterungsmodule Neben bereits in Python integrierten Modulen sind für das Simulationssystem noch einige externe Erweiterungsmodule notwendig oder zumindest äußerst hilfreich. 5.2.1. distutils Dieses erste Modul gehört gleich zur zweiten Kategorie, da es nur den Übersetzungs- und Installationsvorgang managet. Aber ich wünsche wirklich keinem jemals ein System dieser Größe (derzeit um die 50 Module1 ) ohne solch ein Tool installieren zu müssen. Wenn man portable Erweiterungsmodule für Python schreibt, gibt es einige Fragen zu klären, wenn man C-Module hat, wie werden diese auf dieser Plattform übersetzt und wohin muß alles installiert werden. Während das letztere noch relativ leicht herauszufinden ist, indem man Python den Pfad zu seinen Erweiterungen ausgeben läßt, wird die erste Art Problem üblicherweise heutzutage mittels configure und den zugehörigen Tools gelöst. Nur leider ist dies unter Windows nicht so einfach möglich, so daß man dann nicht mehr plattformunabhängig wäre. Abgesehen davon, hat man eine ganze Menge Makefiles zu schreiben. Aber eigentlich ist dies gar nicht so problematisch. Denn auf UNIX stehen die notwendigen CompilerParameter bereits in einer Makefile-Datei im Python-Verzeichnis. Distutils macht nun folgendes, es liest diese Datei und kompiliert vom Nutzer vorgegebene Erweiterungen mit den dort angegebenen Compilern, Linkern und deren Parametern.2 Alles was man als Nutzer noch tun muß, ist ein Setup-Script zu schreiben, welches alle zu installierenden Module auflistet und bei C-Modulen deren Quell-Dateien, die Include- und Bibliotheksverzeichnisse und alle zu linkenden Bibliotheken angibt. Als Beispiel kann hier die Datei setup.py in den Quellen des Systems dienen. Weitere Dokumentation findet man auf der Webseite der Distutils-SIG[16]. Alternativ kann auch ich hierzu Auskunft geben, da ich mit an distutils arbeite und Teile der Windows-Anpassung direkt von mir stammen. Nach dem man dann also dieses Setup-Script erstellt hat, ist es ganz einfach, mit den folgenden 2 Zeilen wird alles übersetzt und installiert: python setup.py build python setup.py install 1 Damit sind Python-Module gemeint, nicht die des Robotersimulators. Dessen Module bestehen meist aus mehreren PythonModulen. 2 Unter Windows ist das nicht so einfach, erstens kompiliert kaum jemand Python unter Windows selbst, so das diese Datei nicht existiert. Außerdem gibt es viele verschiedene Compiler. Unter Windows kann man distutils angeben, welchen Compiler man besitzt bzw. zu benutzen gedenkt. Derzeit existieren Anpassungen für MS Visual C++(Standard), Borland C++ 5.5 und die GNU-C-Compiler cygwin und mingw32. 25 5. Python (Es wird vorausgesetzt, daß man sich im selben Verzeichnis wie setup.py befindet.) Auf genau diese Weise wird neben distutils auch jedes andere Erweiterungspaket installiert, welches für die Installation distutils nutzt. Wichtig: Es muß mindestens Version 0.9.1 benutzt werden, das RSTk-Installationsscript braucht einige der neuen Funktionen, außerdem läuft distutils nur mit Python Versionen >=1.5.2. 5.2.2. pygtk Da als Benutzeroberfläche GTK zum Einsatz kommt, brauchen wir hierzu eine passende PythonAnbindung, diese wird hier durch pygtk[4] realisiert. Es existieren dabei zwei Varianten um GTK zu benutzen, die eine ist eine direkte Umsetzung der GTKAnweisungen und kann auch genau wie diese benutzt werden. Die zweite ist wesentlich eleganter, da sie die GTK-Objekte in echten Python-Objekten kapselt. Zur Programmierung kann man hier eigentlich nur auf die Beispiele im pygtk-Paket selbst oder gleich auf die GTK-Dokumentation[22] verweisen, da es praktisch eine 1:1 Umsetzung des GTK ist. Neben der GTK-Anbindung wird auch gleich ein Interface zum GTK-OpenGL-Widget GtkGlArea[9] mitgeliefert. Wenn man die Oberfläche nicht selbst programmieren möchte, sollte man sich das Programm glade[19] genauer ansehen. Mit glade kann man sich sein GTK-Interface am Bildschirm zusammenklicken und bei Bedarf sogar als C-Code ausgeben lassen. Aufgrund der Tatsache das glade als Speicherformat XML benutzt, wurden relativ schnell auch Code-Generatoren für andere Programmiersprachen entwickelt. Einer davon ist in der Glade Python Code Generator[20], leider ist die im Internet verfügbare Version noch nicht sehr ausgereift, so das sich auf der CD eine wesentlich erweiterte Version des Programmes befindet. Nachdem man seine glade-Dateien nach Python konvertiert hat, muß man noch die SignalHandler-Funktionen programmieren und ist fertig. Die gesamte, bisher im System vorhandene, Benutzeroberfläche wurde auf diesem Weg erstellt, diese Vorgehensweise ist also sehr zu empfehlen. Wichtig: Es muß mindestens Version 0.6.4 benutzt werden, da anderenfalls die notwendige Unterstützung für GtkGlArea noch nicht enthalten ist. 5.2.3. PyOpenGL Für OpenGL findet sich mit PyOpenGL[37] eine komplette Umsetzung des OpenGL-Befehlssatzes für Python. Damit lassen sich praktisch alle herkömmlichen OpenGL-Anwendungen auch in Python realisieren. Natürlich ist die Geschwindigkeit nicht so groß wie bei einem Programm in C. Dafür bietet aber Python die Möglichkeit Änderungen am Programm innerhalb kürzester Zeit durchzuführen, womit es sich hier ideal für die Prototypenprogrammierung und für Lernzwecke eignet. (Man könnte damit OpenGL sogar im interaktiven Modus benutzen.) Im derzeit existierenden System sind nur noch wenige Teile durch Python-OpenGL-Anweisungen realisiert. Der jetzt noch existierende Code realisiert das Setzen von einigen Lichtern und der Ansicht, da der Code zum Heraussuchen dieser Elemente aus dem Objektbaum noch in Arbeit ist. 5.2.4. Numerical Python Numerical Python[31] ist eine Erweiterung, die einerseits wesentlich effektivere Typen von Feldern für Zahlen mitbringt und andererseits jede Menge numerischer Funktionen, das reicht von MatrixOperationen über die Fast-Fourier-Transformation bis hin zum Lösen von Gleichungssystemen. 26 5.2. Erweiterungsmodule Diese Erweiterung wird derzeit noch nicht benutzt, aber in Zukunft werden einige Module es tun. Der eigentliche Grund hierzu ist die bessere und Speicherplatz-sparende Verwaltung von Feldern. Das, als Beispiel in Kapitel 12 benutzte Modul, soll zum Beispiel eine Anzahl von Koordinaten speichern. Da dies auf Python-Ebene geschehen soll, müssen diese Daten irgendwie in einen Python-Datentyp gewandelt werden. Bei der zu erwartenden Anzahl von einigen tausend Zahlen, arbeiten aber normale Python-Listen extrem ineffektiv, so daß hier auf die besseren Feld-Datentypen zurückgegriffen werden soll. 27 5. 28 Python Teil II. Realisierung 29 6. Übersicht 6.1. Python-Integration Nachdem alle zunutzenden Softwarepakete festgelegt wurden sind, stellt sich nun die Frage der Integration. Es gibt hierbei zwei Möglichkeiten: • Entwicklung eines eigenen Programmes, daß auf GTK aufsetzt und Python als eingebettes Modul enthält. • Realisierung des Systems als Sammlung von Erweiterungsmodulen für Python, und Benutzung dieser Module in einem Python-Programm, wobei die Benutzeroberfläche indirekt über PythonErweiterungen auf GTK aufsetzt. Der zweite Ansatz verspricht vorteilhafter zu sein. So kann man mittels Python relativ schnell Prototypen erstellen und das gesamte System kann später wiederum als Basis für Erweiterungen dienen. Die Gesamtstruktur einschließlich aller extern beteiligten Softwarepakete entspricht damit der in Abbildung 6.1 abgebildeten Struktur. Robots Simulation Toolkit GtkGlArea GTK utils modules rstk_threads unique_string callback Objects_3D nodes Externe Bibliotheken Python-Module PyOpenGL Python pygtk scheduler dll xml repository transformation boundingbox geometry box cone cylinder sphere description Khepera controller proximity_sensor Examples OpenGL tracker appearance material View_3D opengl viewpoint light directionallight pointlight spotlight Abbildung 6.1.: Gesamtübersicht 31 6. Übersicht Wie man sieht gibt es außer durch Python und seine Erweiterung pygtk keinerlei direkte Verbindung zum GTK, dadurch ist es, wenn es jemals notwendig werden sollte, möglich das GTK durch ein anderes Grafik-Toolkit zu ersetzen, ohne das dazu die Basis-Module verändert werden müßten. Die einzigen zu ersetzenden Programmteile wären hierbei die Oberflächen der Erweiterungsmodule. 6.2. Module Es werden zwei unterschiedliche Arten von Modulen benutzt. 6.2.1. Basis-Module Die eine Art sind die Basis-Module. Diese Module sind für die grundlegenden Strukturen verantwortlich. Mit Hilfe dieser Module ist es möglich eine Art Simulator zu schaffen, der für allgemeine Zwecke einsetzbar. Dazu benötigt man einerseits ein passendes Datenmodell, hier eine Baumstruktur, die Möglichkeit diese abzuspeichern (XML) und einen Scheduler, der die Simulation steuert. Da dieses Grundgerüst aber allein noch nicht viel Sinn macht, kommen mindestens noch die Erweiterungen zur Verwaltung von Erweiterungsmodulen für den Simulator und deren Kommunikation mit den Basismodulen und untereinander dazu. Diese Basis-Module sind, die in Abbildung 6.1 im Abschnitt utils enthaltenen Module. Die komplette Auflistung der Module und deren Funktionsweise ist im Kapitel 7.1 enthalten. 6.2.2. Erweiterungsmodule Die Erweiterungsmodule dienen der eigentlichen Realisierung des gewünschten Anwendungszwecks der Simulation. In diesem Fall ist das die 3D-Umgebung und die sich darin befindlichen Roboter und deren Steuerungen. Notwendige Module Im einzelnen sind Module für folgende Aufgaben notwendig: • 3D-Objekte, deren Geometrie und Aussehen. • Visualisierung dieser 3D-Objekte. • Hilfsmodule zur Visualisierung wie zum Beispiel Lichtquellen und Kameraspezifikationen. • Module, die die Robotersensoren und -aktuatoren realisieren. • Steuerungsmodule, die das eigentliche Steuerungsprogramm an den Simulator koppeln. Eine Auswahl, der für diesen Zweck bereits realisierten Module, kann man dem modules-Abschnitt in der Abbildung 6.1 entnehmen. Die genaue Funktionsweise und Aufgabe wird in Kapitel 7.2 beschrieben. 32 6.3. Das zentrale Repository Allgemeiner Aufbau eines Erweiterungsmoduls Im wesentlichen entsprechen die Erweiterungsmodule normalen Pythonmodulen, wenn man einmal davon absieht, daß die Module sich selbst beim Simulator anmelden müssen. Die Entwicklung eigener Module hat aber schnell gezeigt, daß ein einzelnes Pythonmodul hier nicht ausreicht um alle Funktionen eines Simulator-Erweiterungsmoduls abzudecken. So benötigen viele Module der Geschwindigkeit halber eine Realisierung bestimmter Teile in C und bieten dann auch ein entsprechendes C-Interface für andere Module, die so nicht denn Umweg über Python gehen müssen, um irgendwelche Funktionen aufzurufen. Andererseits sind bestimmte Teile der Module leichter in Python zu programmieren. Als weiterer Aspekt muß die Benutzeroberfläche des Moduls für Konfigurationszwecke beachtet werden, insbesondere da diese zum Teil automatisiert erstellt werden soll. Als Resultat ergibt eine Aufteilung eines RSTk-Moduls in verschiedene Python-Module. Abbildung 6.2 demonstriert dies an einem Beispielmodul namens controller. Wie man sieht organisiert das Modul-Interface-Objekt, interne Koordination und XML-Interface Benutzeroberfläche controller_gui controller (Python) _controller (C) instance_config (Python) module_config (Python) Objekt-Definition und C-Interface Abbildung 6.2.: Aufbau eines RSTk-Moduls Pythonmodul controller alle Verbindungen nach außen. Die in C programmierten Teile werden durch das Python-C-Modul _controller zur Verfügung gestellt, und mittels der Anweisung „from _controller import *“ in das Pythonmodul integriert. Auch die Konfigurationsdialoge werden durch jeweils eigene Pythonmodule realisiert, aber dazu folgt weiter unten mehr. Diese eben erklärte Struktur bildet die Grundlage für fast alle realisierten Erweiterungsmodule, wobei die jeweils unnötigen Teile weggelassen worden, und ist daher als Standard für zukünfige Erweiterungen anzusehen. Es existiert nur ein einziges Modul (description) das aufgrund seiner Einfachheit (es speichert nur Text) alle Teile in einem einzigen Python-Modul unterbringt. 6.3. Das zentrale Repository Da die Simulator-Erweiterungsmodule eigentlich nur etwas erweiterte Pythonmodule sind, kann man sie einfach über den normalen Import-Mechanismus in Python laden und benutzen. Das Problem hierbei ist, das man dann aber nie weiß, welche Module im System geladen sind, und selbst wenn man das wüsste, wie diese dazu zu bringen sind, zum Beispiel ihre Daten zu speichern. Aus diesem Grund wurde ein zentrales Repository geschaffen, in welchem sich die Erweiterungsmodule anmelden müssen. Dazu hat jedes Modul, welches irgendwelche Konfiguration ermöglichen möchte oder Daten laden und speichern muß, beim Laden des Moduls die Funktion register_module(name,interface) aufzurufen. Der zweite Parameter stellt hierbei, die Schnittstelle zum System her. 33 6. Übersicht 6.3.1. Allgemeine Modul-Schnittstelle Um diese Schnittstelle zu realisieren, hat das Modul die folgende Klasse oder eine davon abgeleitete Klasse zu instantiieren und beim Registrieren zu übergeben. class module_template: def __init__(self,name): pass def get_name(self): return None def get_module_loader(self,root): return None def get_instance_loader(self,root,node): return None def save_module(self,xmlsaver): pass def save_instance(self,xmlsaver,data): pass def get_module_config(self): return None def get_instance_config(self,node): return None (self ist der bei anderen Programmiersprachen vorhandene, aber versteckte this Parameter der Methoden. pass ist das Python-Äquivalent zu {} in C. Da Python die Blockbildung im Quelltext allein anhand der Einrückung feststellt, gibt es ein Extra-Schlüsselwort für einen leeren Block. None ist das Python-Äquivalent zu NULL in anderen Programmiersprachen.) Die Standardimplementation der Methoden besteht darin, entweder nichts zu tun oder bei den Konfigurationsdialogen einen Dialog zu erzeugen, der besagt das dieses Modul keinen Dialog zur Verfügung stellt. (Der abgedruckte Quelltext ist also nur das reine Interface. Dies bezieht sich auch auf alle hier folgenden Interfaces.) Die Methoden haben folgende Bedeutung: __init__(self,name) Konstruktor der Klasse, name ist hierbei der Modulname. get_name(self) Abfrage des Modulnamens. get_module_loader(self,root) Anforderung eines Objekts, welches die Moduldaten lädt. root ist hierbei die oberste Ebene der XML-Loader-Objekte, über dieses oberstes Objekt läßt sich ein anderes XML-Loader-Objekt zum Laden eines Teilbaumes einsetzen. (Für ein Beispiel sehe man sich den Quelltext des RSTk.utils.xml-Moduls an.) get_instance_loader(self,root,node) Anforderung eines Objektes, welches die Nodespezifischen Daten liest und an die Node node anfügt. save_module(self,xmlsaver) Speichern der Moduldaten mit Hilfe des Objektes xmlsaver. save_instance(self,xmlsaver,data) Speichern der Moduldaten einer Node mit Hilfe des Objektes xmlsaver. get_module_config(self) Anforderung Konfigurationsdialog zur Verfügung stellt. eines Objektes, welches den Modul- get_instance_config(self,node) Anforderung eines Objektes, welches den Modulspezifischen Nodes-Konfigurationsdialog für die Node node zur Verfügung stellt. Die Interfaces, der in den Methoden benutzten Objekte, werden in den folgenden Abschnitten erläutert. 34 6.3. Das zentrale Repository 6.3.2. XML-Interfaces Das XML-Interface ist relativ einfach gehalten. Zuerst soll hierbei das Speichern behandelt werden. Die Module bekommen dazu ein Objekt mit folgendem Interface übergeben: class _xml_saver: def starttag(self, tag, attributes={}): pass def endtag(self, tag): pass def data(self, data): pass Die einzelnen Methoden haben folgende Auswirkungen: starttag(self, tag, attributes={}) Dies schreibt einen öffnenden XML-Tag in die Ausgabedatei, dabei werden Einträge des, als Parameter attributes übergebenen, Verzeichnisses1 als Attribute dieses Tags gespeichert. (Zum Beispiel <test name="wert">.) endtag(self, tag) Diese Methode schreibt den schliessenden Tag in die Ausgabedatei. (</test>) data(self, data) Mit dieser Methode kann beliebiger Text in die Ausgabedatei geschrieben werden. Dabei werden allerdings vorher alle XML-spezifischen Sonderzeichen entsprechend umgewandelt. Nach der Speicherfunktionalität soll nun das Laden realisiert werden. Zu allererst ist es wichtig zu wissen, daß der System-eigene Parser, die zu ladende Datei von sich aus wieder in die Tags und die Daten zerlegt. Je nach gefundenem Element wird die passende Funktion im Loader-Interface aufgerufen und das Element dabei übergeben. Das Objekt, welches dieses Loader-Interface zur Verfügung stellt, hat dann seine Daten aus diesen Einzelteilen zu rekonstruieren. Für eine ausführlichere Beschreibung des Prinzips sollte man sich zum Beispiel die SAX-Webseite[43] ansehen. Das Objekt, welches die Moduldaten laden soll, hat folgende Klasse abzuleiten und die entsprechenden Funktionen zu überschreiben. class xml_loader_interface: def starttag(self, tag, attributes): pass def endtag(self, tag): pass def data(self, data): pass def close(self): pass Die Methoden sind hierbei die passenden Gegenstücke des Interfaces zum Speichern. Die einzige Ergänzung bildet hier die Funktion close, die aufgerufen wird wenn keine Daten mehr für dieses Modul verfügbar sind. Diese Vorgehensweise ist notwendig, weil grössere Datenblöcke mit Hilfe von mehreren Aufrufen der Funktion data übergeben werden könnten. In diesem Fall würde man diese erst alle verketten und dann insgesamt übernehmen, dazu ist es aber notwendig zu wissen, ab wann keine Daten mehr kommen. 1 In Python bestehen Verzeichnisse aus einer Menge von Einträgen, die jeweils einem Schlüssel einen Wert zuordnen. {} ist hierbei ein leeres Verzeichnis. Für weitere Informationen bediene man sich der Python-Dokumentation[38]. 35 6. Übersicht 6.3.3. Konfigurationsdialoge Um Konfigurationsdialoge zu ermöglichen, muß ein Objekt mit folgendem Interface implementiert werden. class __basic_config_template: def get_widget(self): return None def get_accelgroup(self): return None module_config_template = __basic_config_template instance_config_template = __basic_config_template Die Interfaces für die Modul- und Nodes-Konfiguration sind derzeit identisch. Die Methoden haben folgende Funktion: get_widget(self) Diese Methode muß ein GTK-Widget zurückgeben, welches dann in den Konfigurationsdialog eingeblendet wird. Das GTK-Widget kann hierbei jedes beliebige GTK-Widget sein, insbesondere auch eines das andere GTK-Elemente enthalten kann. get_accelgroup(self) Über diese Methode können die benutzten Tastenkürzel übergeben werden, damit diese ebenfalls in das übergeordnete Fenster integriert werden können. Neben dem oben schon erwähnten eigenen Standard für den Aufbau der Erweiterungsmodule, wurde eine ähnliche Aufteilung auch für die Konfigurationsdialoge realisiert. allgemeine Steuerfunktionen functions.py __init__.py Interface nach außen gui.py *Handlers.py generierte Benutzeroberfläche Ereignishandler Abbildung 6.3.: Zusammenarbeit der Dateien eines Konfigurationsdialoges Die Konfigurationsdialoge werden hierbei zuerst mit dem Interface-Builder glade[19] erstellt und dann mittels des Glade Python Code Generators[20] in Python-Code übersetzt. (Die genaue Vergehensweise ist im Beispiel zur Erstellung eines Erweiterungsmoduls in Kapitel 12.2.3 beschrieben.) Dabei entstehen zwei Dateien gui.py und *Handlers.py, wobei in der zweiten noch die Signal-Handler fertig zu programmieren sind. Die Datei functions.py ist selbst programmiert und stellt alle sonst notwendigen Funktionen zur Verfügung. Am Ende wird alles in der Datei __init__.py zusammengefasst. 1 2 from functions import * from gui import * Durch diese Maßnahme kann diese Kombination mehrerer Dateien von außen wie ein einzelnes Pythonmodul behandelt werden. 36 7. RSTk-Module Nach dem Gesamtüberblick sollen nun zu allen wesentlichen Modulen, die Arbeitsweise und der Verwendungszweck erläutert werden. Aufgrund der Platzbeschränkung für die gesamte Arbeit mußten die Erläuterungen allerdings auf ein Minimum beschränkt werden. Für weitere Einzelheiten bietet sich einerseits die Einsicht in die Quelltexte auf CD an, und andererseits speziell für die 3D-Erweiterungsmodule das Studium der Dokumentationen ähnlich aufgebauter Systeme und Standards, wie zum Beispiel Java3D[26] und VRML[51]. 7.1. Basis-Module Die Basis-Module realisieren die Grundstrukturen, wie sie bereits in Kapitel 3 vorgestellt wurden. Mit Hilfe dieser Module ist es möglich Baumstrukturen aufzubauen, wobei deren Nodes beliebige, durch Namen referenzierbare, Daten aufnehmen können. Weiterhin ist ein Callback-Objekt verfügbar, welches zur Kommunikation zwischen verschiedenen Modulen nutzbar ist. Und als wichtigster Teil wird hier der eigentliche Scheduler des Simulationssystems realisiert. 7.1.1. rstk_threads Zweck Kapselung einiger Thread-spezifischer Python-Funktionen. Funktionsweise Dieses Modul ist eines der wichtigstes Module im gesamten System, da ohne dieses Modul kein sinnvolles Arbeiten mit eigenen Threads unter Python möglich ist. In einigen internen Teilen ist Python darauf angewiesen, daß immer nur ein Thread Zugriff auf die internen Datenstrukturen hat. Dies wird über einen zentralen Lock-Mechanismus organisiert. Python koordiniert seine Arbeit intern über einen Zeiger auf dessen Status-Daten. Dieser Zeiger wird vom jeweils aktuellen Thread gesetzt, sobald dieser die Kontrolle erlangt. In eigenen Extensions kann man dann diese Kontrolle zeitweilig abgeben, zum Beispiel wenn man auf ein Netzwerkverbindung1 oder ähnliches wartet. Dieser Bereich ist dann durch die Anweisungen Py_BEGIN_ALLOW_THREADS und Py_END_ALLOW_THREADS abzugrenzen. Und da fangen auch schon die Probleme an, erstens lassen sich diese beiden Anweisungen nicht verschachteln und zweitens muß man der aktuelle Python-Thread2 sein. Beide Voraussetzungen können nicht immer grarantiert werden, wenn man eigene Threads mit ins Spiel bringt, die unabhängig 1 Unter X können OpenGL-Befehle unter Umständen direkt über das Netzwerk an den X-Server weitergereicht werden. Damit fallen OpenGL-Befehle zum Beispiel in diese Kategorie. Python-Thread soll ab hier ein Thread bezeichnet werden, der gerade die Kontrolle über den Interpreter hat, oder zumindest von diesem gestartet wurde und deshalb irgendwo einen Thread-Status abgelegt hat. Nicht-Python-Threads sind folglich Threads ohne eigenen Thread-Status. 2 Als 37 7. RSTk-Module von Python laufen, aber bisweilen auch Routinen aufrufen, die auch Python aufrufen kann oder gar den Python-Interpreter selbst aufrufen wollen. Die Lösung liegt letztlich darin, daß man die oben erwähnten Anweisungen einkapselt und nur noch aufruft wenn die richtigen Voraussetzungen vorliegen (Python-Thread und 1. Schachtelungsebene). Da es allerdings keine Möglichkeit gibt dies über Python direkt herauszufinden, mußte hier eine andere Lösung gewählt werden. Die Vorgehensweise ist nun folgende, es werden zwei threadspezifische Datenelemente erzeugt. Wenn diese nicht gesetzt werden, haben diese für neu erzeugte Threads jeweils den Wert 0. Dies wird hier nun als Kennung für einen Python-Thread und dessen 1.Schachtelungsebene angesehen. Für selbsterzeugte Threads (Nicht-Python-Threads) wird die Kennung auf 1 gesetzt, womit sie eindeutig unterscheidbar werden. Durch diese Kennzeichnung kann man nun folgendes realisieren, erstens kann man die Verschachtelungstiefe der Threadfreigabe überwachen (in dem sie in den threadsezifischen Daten mitzählt wird) und dann nur auf der ersten Ebene die problematischen Befehle Py_{BEGIN|END}_ALLOW_THREADS ausführt. Zweitens kann man so feststellen, ob für einen Thread der Python direkt aufrufen will, erst Status-Daten für den Thread erzeugt werden müssen oder ob der Aufruf direkt ausgeführt werden kann, weil der Aufruf dieser Routine ursprünglich von Python stammte. 7.1.2. unique_string Zweck Bereitstellung von einmaligen Strings Funktionsweise Verschiedene andere Module benutzen Namen um ihre Unterobjekte zu referenzieren. Da ein Stringvergleich sehr aufwendig ist, wurde mit diesem Modul eine Möglichkeit geschaffen, einen normalen String in einen einmaligen String umzuwandeln. Einmalig bedeutet hierbei, daß wenn dieselbe Zeichenkette mehrmals umgewandelt wird, jedesmal dasselbe Objekt als Resultat zurückgegeben wird. Durch diese Vorgehensweise kann der Stringvergleich durch einen Vergleich der Objektreferenzen (in C sind das Zeiger) ersetzt werden. Das eigentliche Objekt UniqueString ist hierbei nur eine Kapselung des Pythontyps PyStringObject. Python benutzt intern dasselbe Verfahren um auf seine Verzeichnisse zuzugreifen, so daß es nahe lag dies zu übernehmen. Da aber Python nicht alle Strings in seine interne Liste übernimmt, wurde hier ein Weg geschaffen Python explizit dazu anzuweisen, diesen String aufzunehmen und damit eine einmalige Referenz anzulegen. 7.1.3. callback Zweck Realisierung des Callback-Mechanismus. Funktionsweise Die Callback-Objekte speichern intern eine Liste der verfügbaren Slots und zu diesen Slots eine Liste der registrierten Funktionen. Die Slots sind dabei per Name referenzierbar. Es gibt 3 Funktionsgruppen, eine zur Verwaltung der Slots (Anlegen, Löschen), eine um CallbackFunktionen zu managen (Eintragen, Löschen) und eine Funktion zum Auslösen eines bestimmten Slots des Callbacks. Beim Auslösen eines Slots werden alle dort registrierten Funktionen der Reihe nach abgearbeitet. 38 7.1. Basis-Module Das Callback-Objekt ist sowohl von C als auch von Python aus nutzbar, dies bedeutet auch das neben C-Funktionen auch Python-Funktionen von einem solchen Callback aufgerufen werden können. 7.1.4. nodes Zweck Bereitstellung von Node-Objekten zur Erstellung von Baumstrukturen. Funktionsweise Ein Node-Objekt kann einem anderen Node-Objekt unterstellt werden, wobei jede Parent-Node mehrere Child-Nodes besitzen darf, dadurch ist es möglich eine Baumstruktur zu konstruieren. Jedes Nodes-Objekt besitzt hierbei einen Namen, über den es bei Bedarf von der jeweils übergeordneten Ebene referenziert werden kann. Weiterhin besitzt das Node-Objekt ein internes Verzeichnis in dem, über Namen referenzierte, Daten abgelegt werden können. Diese Daten können beliebige, auch selbst definierte, PythonObjekte sein. Das nodes-Modul besitzt ein zentrales Callback-Objekt über welches die Erzeugung oder Vernichtung eines Node-Objekts, die Änderung der Stellung im Baum oder die Veränderung der zugeordneten Daten signalisiert werden. 7.1.5. scheduler Zweck Ablaufsteuerung der Simulation. Funktionsweise Die Funktionsweise entspricht der bereits im Kapitel 3.4.2 vorgestellten. Die abzuarbeitenden Funktionen werden mit Angabe ihres Startzeitpunktes und zugehörigen benutzerdefinierten Daten registriert und dann vom Scheduler entsprechend abgearbeitet. Registrierte Funktionen werden bei deren Beendigung automatisch vom Scheduler ausgesondert. Mit Hilfe der, bei der Registrierung erhaltenen, ID kann eine Funktion jederzeit abgebrochen werden. Die laufenden Funktionen müssen mittels checkpoint und request_io ihren Zeitbedarf und ihre Ein- oder Ausgaben beim Scheduler anmelden. Innerhalb dieser beiden Funktionen wird dann eventuell der Thread angehalten, um andere vom Scheduler verwaltete Funktionen weiterlaufen zu lassen. Der Scheduler kann in zwei unterschiedlichen Betriebsarten betrieben werden, eine synchrone bei der erst dann vom Aufruf zurückgekehrt wird, wenn die Simulation aus irgendeinem Grund gestoppt wurde, und eine asynchrone Betriebsart in der die Simulation in einem eigenem Thread abläuft. Die synchrone Betriebsarten ist hierbei für den Scriptbetrieb besonders vorteilhaft. Das Stoppen der Simulation kann hierbei entweder durch Setzen einer Stopzeit für die Simulation geschehen, durch eine der abgearbeiteten Funktionen und durch externe Überwachung, in dem einer der vorhandenen Callbacks zur Überprüfung und dem Anhalten der Simulation bei bestimmten Ereignissen verwendet wird. Der asynchrone Betrieb kommt beim Betrieb mit der Benutzeroberfläche zum Einsatz. Auch der Scheduler besitzt sein eigenes Callback-Objekt, über welches er den Wechsel seines Status meldet (runmode_changed) und die Aktualisierung der Welt einleitet (update_world) beziehungsweise deren Abschluß bekannt gibt (update_world2.) Weiterhin wird die Veränderung der Simulationszeit über update_simtime und das Rücksetzen über reset signalisiert. 39 7. RSTk-Module 7.1.6. dll Zweck Abstrakte Schnittstelle zu DLL’s und Shared Objects Funktionsweise Dieses Modul stellt eine einheitliche Schnittstelle zum Laden von DLL’s und Shared Objects zur Verfügung, da deren Behandlung und der notwendige Befehlssatz sonst vom Betriebssystem abhängig wäre. Ein solches DLL-Objekt wird mit Angabe des Dateinamen erzeugt, und wenn das Laden der DLL erfolgreich war, stellt es eine Funktion zur Verfügung mit deren Hilfe die Adressen der, von dieser DLL exportierten, Funktionen erfragt werden können. 7.2. Erweiterungsmodule Die folgenden Module sind alles Erweiterungsmodule in dem Sinne, daß sie zwar eine 3D-Umgebung modellieren, aber zum eigentlichen Betrieb des Simulators überhaupt nicht notwendig sind. Die Simulator könnte theoretisch auch für komplett andere Anwendungsgebiete zum Einsatz kommen. Da aber Roboter in einer 3D-Umgebung simuliert werden sollen, dürfen hier die notwendigen 3DDarstellungselemente nicht fehlen. Die meisten 3D-Elemente wurden, von ihrer Struktur her, dem VRML-Standard[51] entlehnt, so daß man diesen als Dokumentation der einzelnen Objektattribute benutzen kann. Wenn im nachfolgenden Text von Objekten die Rede ist, so ist hierbei immer das durch das jeweilige Modul realisierte Objekt gemeint. 7.2.1. 3D-Objekte Die nachfolgenden Module dienen zur Realisierung der 3D-Grundelemente und deren wichtigsten Eigenschaften. transformation Zweck Transformationsmatrix zur Beschreibung der Position. Funktionsweise Dieses Objekt speichert eine Transformationsmatrix. Mit Hilfe der Transformationsmatrix wird die Position und die Ausrichtung des zugehörigen 3D-Objektes festgelegt. Die Matrix muß hierzu einer Node mit entsprechenden Daten zugeordnet werden. Die Matrix bezieht sich auf die Geometriedaten der Node selbst, als auch auf alle untergeordneten Nodes. Veränderungen der Matrix werden über den zentralen Callback des nodes-Moduls durch den Slot transformation_changed gemeldet, Voraussetzung ist , daß die Matrix einer Node zugeordnet ist. geometry Zweck Geometriedaten des 3D-Objektes. 40 7.2. Erweiterungsmodule Funktionsweise Dieses Modul ist als eine Art Basisklasse zur Speicherung der Geometriedaten zu verstehen. Es besitzt Funktionen um sich selbst mittels OpenGL zu zeichnen, die Bounding-Box zu ermitteln und festzustellen, ob ein gegebener Strahl das Objekt trifft. (Die letzten zwei Funktionen sind derzeit noch nicht implementiert.) Als Datenelemente besitzt dieses Objekt eine Referenz auf ein Python-Objekt und einen Zeiger auf eine Funktionstabelle, über die die obengenannten Funktionen mit Hilfe des Python-Objekts realisiert werden3 . Die eigentlichen 3D-Objekte legen ihre Daten und Funktionszeiger dann im Geometrie-Objekt ab. Derzeit sind Quader (box), Kegel (cone), Kugel (sphere) und Zylinder (cylinder) verfügbar. Zur Abbildung dieses Objekte in das XML-Datenformat wurde sich hierbei an den Vorschlägen für den neuen VRML200x-Standard[50] orientiert. Auch dieses Modul benutzt den zentralen Nodes-Callback (Slot geometry_changed) um alle Veränderungen der interne Daten zu melden. appearance Zweck Aussehen der Objekte. Funktionsweise Dieses Objekt speichert die Eigenschaften, die das Aussehen eines Objektes bestimmen, Material, Textur und Texturtransformation. Die letzteren beiden wurden allerdings noch nicht realisiert. Die Struktur entsprecht auch hier der des VRML. Das enthaltene Material-Objekt speichert die Farbe bei direkter Beleuchtung, im Umgebungslicht und die Farbe, in der das Objekt eventuell selbst leuchtet. Die Einstellungen für das Aussehen gelten jeweils auch für alle im Objektbaum untergeordneten Objekte, wenn diese diesbezüglich keine andere Einstellung tätigen. boundingbox Zweck Bounding-Box einer Node. Funktionsweise Dieses Modul erscheint nicht in den abgespeicherten Dateien und ist auch sonst nicht vom Benutzer wahrnehmbar, es wird nur für interne Zwecke verwendet. Dieses Modul verwaltet die Bounding-Box einer Node. Dazu muß es dieser zugeordnet werden. Die Bounding-Box umfaßt neben der eigenen Geometrie auch die aller untergeordneten Nodes im Baum. Um jederzeit die korrekten Größe zu garantieren, überwacht dieses Modul den zentralen Nodes-Callback auf alle Signale, die irgendeinen Einfluß auf die Bounding-Boxen haben könnten. Dies betrifft Änderungen der Baumstruktur selbst, wie auch Änderungen der Geometrie oder der Transformationsmatrizen der Nodes. Beim Auftreten dieser Ereignisse werden die entsprechenden Bounding-Box-Objekte als ungültig markiert. Beim nächsten Versuch diese Objekte zu benutzen, werden diese dann automatisch auf den neuesten Stand gebracht. (Dieses Modul ist noch nicht komplett implementiert.) 3 In C++ hätte man diese Klasse abgeleitet, aber in C muß man virtuelle Funktionen etwas anders nachbilden. Möglicherweise wird eine verbesserte Version C++ benutzen. 41 7. RSTk-Module 7.2.2. 3D-Visualisierung Die folgenden Module dienen ausschließlich der Darstellung der 3D-Umgebung auf dem Bildschirm. Für die Simulation sind sie nicht notwendig. light Zweck Lichtquelle. Funktionsweise Dieses Modul realisiert eine Lichtquellen in der 3D-Umgebung. Da auch hier mehrere Unterarten existieren, wurde auch eine zweigeteilte Lösung gewählt, wie bereits oben beim Geometrie-Modul beschrieben. Folgende Lichtquellentypen sind verfügbar: Gerichtetes paralleles Licht (directionallight), Punktlichtquellen (pointlight) und gebündeltes Licht (spotlight). Die Lichtquellen müssen sich selbst über OpenGL-Befehle einrichten können, dazu bekommen sie als Parameter die Nummer einer freien OpenGL-Lichtquelle und müssen diese dann entsprechend konfigurieren. viewpoint Zweck Definition einer Kamera. Funktionsweise Diesen Modul verwaltet eine Kamera, wobei hiermit deren Standpunkt, Ausrichtung und Blickwinkel gemeint ist. Das Modul sorgt dafür, daß die aktuelle OpenGL-Matrix passend zu den Kamera-Eigenschaften eingestellt wird. Dabei ist zu beachten, daß man hier vorher in OpenGL die Projektionsmatrix auswählt. opengl Zweck OpenGL-Displayliste. Funktionsweise Dies ist wieder ein internes Modul von dem außen nichts zu sehen ist. Dieses Modul verwaltet für jede Node im Objektbaum eine OpenGL-Displayliste. Diese wird jeweils aus den vorhandenen Daten für Transformationsmatrix, Geometrie und Aussehen und den jeweiligen Displaylisten eventuell vorhander untergeordneter Nodes erstellt. Wenn sich diese Daten ändern, wird die Displayliste als ungültig markiert, und muß dann mittels update wieder regeneriert werden. Dieser explizite Schritt ist notwendig, damit sicher gestellt ist, daß auch ein aktueller OpenGLKontext vorhanden ist. Wenn eine Displayliste ungültig wird, wird gleichzeitig ein Modul-interner Zähler erhöht. Mit Hilfe dieses Zählers ist von außen feststellbar, ob sich seit der letzten Abfrage des Zählers etwas verändert hat, und eventuell ein Neuzeichnen notwendig ist. Dieses Neuzeichnen kann mittels der Anweisung execute ausgelöst werden, wobei die Displayliste, und mit ihr alle enthaltenen Displaylisten der untergeordneten Nodes, in OpenGL zum rendern aufgerufen wird. Beim Ausführen der execute-Funktion wird allerdings vorher über den Modul-eigenen Callback das Signal draw_userobjects ausgelöst, so daß fremde Module die Gelegenheit bekommen, ihre eigenen Daten mittels OpenGL-Anweisungen rendern zu lassen. 42 7.2. Erweiterungsmodule 7.2.3. Khepera-Controller Die eigentliche Simulation der Khepera basiert derzeit auf den folgenden zwei Modulen. Die genaue Realisierung der Simulation und der Einbindung des Controller-Programmes wird in Kapitel 9 erklärt. controller Zweck Khepera-BIOS-Interface und -Simulation Funktionsweise Dieses Modul bildet auf der einen Seite die Schnittstelle zum externen Controller. Auf der anderen Seite simuliert es die Aktionen des Khepera und ermittelt die Werte der Sensoren. Die externe Controller-DLL wird mit Hilfe des dll-Moduls geladen, und dann werden die Adressen der Funktionen set_Khepera_Interface und main ermittelt. Mit Hilfe der ersten Funktion setzt sich das controller-Objekt selbst als externes Interface für die Controller-DLL ein. Danach wird die main-Funktion beim Scheduler registriert. Die Bewegung des Roboters wird ausgehend von der gesetzten Geschwindigkeit der Räder und der Zeitdauer seit der letzten Aktualisierung berechnet, und dann durch Manipulation der Transformationsmatrix ausgeführt. Zur Ermittlung der Sensorwerte werden die zugeordneten Sensoren abgefragt. Deren Funktionsweise beschreibt der folgende Abschnitt. (Da die Sensoren noch nicht implementiert sind, werden noch keine Sensorwerte abgefragt und auch keine externen Controller-Programme geladen, der Roboter fährt derzeit einfach im Kreis.) proximity_sensor Zweck Entfernungssensor des Khepera. Funktionsweise Die Entfernungsmessung soll folgendermaßen funktionieren: Vom Sensor ausgehend wird ein Strahl berechnet, danach dieser wird gegen alle anderen Objekte getestet, ob diese berührt werden. An diesem Punkt kommen die Bounding-Boxen ins Spiel, da mit deren Hilfe die Anzahl zu testender Objekte zum Teil erheblich reduziert werden kann. Wenn die Bounding-Box nicht berührt wird, kann auch keines der enthaltenen Objekte dies tun. Wenn Berührungen festgestellt wurden, wird die mit der kürzesten Distanz gespeichert. Aus der Distanz kann dann ein Sensorwert berechnet werden. In dieser Berechnung sollen dann auch andere Einflüsse wie zum Beispiel simuliertes Rauschen realisiert werden. (Dieses Modul ist noch nicht implementiert.) 43 8. Programmoberfläche 8.1. Hauptfenster Nach dem Starten des Programmes erscheint das in Abbildung 8.1 dargestellte Fenster. Dieses stellt die zentrale Steuerung des gesamten Simulationssystems dar. Abbildung 8.1.: Hauptbildschirm Wie man sieht, ist die Beschriftung komplett in Englisch. Der Simulator selbst soll irgendwann als freie Software jedermann zur Verfügung stehen, so daß mit Hinsicht auf dieses Ziel bereits jetzt alle Bedienelemente (und auch die Quelltexte) in Englisch gehalten sind. 8.1.1. Menüpunkte Die vorhandenen Menüs enthalten im einzelnen folgende Punkte und Funktionalität. File Funktionen zur Dateiarbeit New Komplettes Löschen des Objektbaumes und Rücksetzen des Schedulers. Nach dieser Aktion besteht der Objektbaum aus einer einzelnen leeren Node. Da aber derzeit keine Möglichkeit besteht, den Objektbaum im Programm aufzubauen, ist damit kein sinnvolles Arbeiten möglich. Open Laden eines Objektbaumes aus einer bestehenden XML-Datei. Dabei werden in der XMLDatei benannte, aber bisher noch nicht registrierte, Module geladen und initialisiert. Gleichzeitig wird der Dateiname für späteres Speichern intern abgelegt. Falls das Laden fehlschlägt, sind aber eventuell bereits einige der registrierten Module mit neuen Daten initialisiert worden. Save Der existierende Objektbaum wird in die Datei gespeichert, aus der er ursprünglich geladen wurde. Falls er aus einer Datei geladen wurden war, zum Beispiel nach Aktivierung des 44 8.1. Hauptfenster Menüpunktes „Neu“, wird dieselbe Aktion wie bei Aktivierung des Menüpunktes „Save As“ ausgeführt. Save As Speichern des Objektbaumes unter einem neuen Namen. Nach Beendigung dieser Aktion ist der gewählte Dateiname gleichzeitig der aktuelle für das System. Quit Dieser Menüpunkt beendet das System. Auch durch Schliessen des Hauptfensters läßt sich das Programm beenden, wenn die dabei eirscheinende Sicherheitsabfrage entsprechend beantwortet wird. Simulation Über dieses Menü wird die Simulation, oder besser gesagt der Scheduler, gesteuert. Start Starten der Simulation. Stop Anhalten der Simulation. Step Ausführen eines Schrittes der Simulation. Ein Schritt endet, wenn die Simulationszeit weitergeschaltet wird. Wie lang die simulierte Zeitspanne für einen Schritt ist, hängt demzufolge von den simulierten Roboter-Controllern ab. Reset Rücksetzen der Simulation. Dabei wird die Simulationszeit auf 0.0 gesetzt und alle simulierten Roboter-Controller werden zurückgesetzt. View Dieses Menü enthält die vorhandenen Visualisierungsarten. Derzeit existiert nur die 3D-Ansicht. 3D-View Dieses Menü bringt ein 3D-Ansichtsfenster entsprechend Abbildung 8.5 zur Anzeige. Wenn bereits ein solches angezeigt wird, wird es aktiviert und in den Vordergrund gebracht. Config Diese Menü dient zur Aktivierung der Konfigurationsdialoge. Falls der entsprechende Dialog bereits aktiviert ist, wird er bei erneuter Aktivierung des Menüpunktes in den Vordergrund geholt. Modules Dieser Menüpunkt aktiviert den Modul-Konfigurationsdialog (Abbildung 8.3.) Nodes Dieser Menüpunkt aktiviert den Konfigurationsdialog für einzelne Nodes des Objektbaumes (Abbildung 8.4.) Help Hier findet man allgemeine Informationen zum Programm. Ein echtes Hilfesystem ist derzeit nicht geplant. About Dieser Menüpunkt bringt ein Fenster entsprechend Abbildung 8.2 zur Anzeige, welches einige allgemeine Informationen zum Programm enthält. 8.1.2. Toolbar Die Schaltflächen im Toolbar entsprechen folgenden Menüpunkten: (von links nach rechts) 1. File–New 2. File–Open 3. File–Save 4. Simulation–Start 5. Simulation–Stop 45 8. Programmoberfläche Abbildung 8.2.: Info-Bildschirm 6. Simulation–Step 7. View–3D-View 8. Config–Modules 9. Config–Nodes 10. Help–About 8.1.3. Sonstige Anzeige- und Bedienelemente Im Hauptteil des Fensters finden sich folgende Elemente. • oben links - Die Anzeige des aktuellen Status des Schedulers. (stopped=angehalten, running=läuft, shutdown=warten auf Abschluß noch laufender Threads) • oben rechts - Die aktuelle Simulationszeit des Schedulers. • mitte links - Anzeige des gewünschten Geschwindigkeit der Simulation relativ zur realen Zeit. • mitte rechts - Anzeige der tatsächlich erreichten Geschwindigkeit. • unten links - Schieberegler zum Einstellen der gewünschten Geschwindigkeit. • unten rechts - Fullspeed-Schaltfläche, wenn diese aktiviert ist, läuft die Simulation mit der maximalen Geschwindigkeit.1 1 Die maximale Geschwindigkeit ist rechnerabhängig, bei bisherigen Tests lag sie in der Größenordnung von 20 bis 90facher Geschwindigkeit relativ zur realen Zeit. Diese Ergebnisse beziehen auf einen Rechner mit AMD K6-II 400MHz, 46 8.2. Konfigurationsdialoge Die Anzeige des Scheduler-Status wird jeweils bei dessen Änderung aktualisiert. Die Simulationszeit und die erreichte Geschwindigkeit werden, sobald die Simulation läuft, aller 100ms aktualisert, 8.2. Konfigurationsdialoge Es gibt im System zwei Arten von Konfigurationsdialogen, einen der die Module als ganzes betrifft und einen zweiten der für die an die Nodes im Objektbaum angehängten Daten zuständig ist. 8.2.1. Für das gesamte Modul Der Modul-Konfigurationsdialog dient der Konfiguration der allgemeinen Moduldaten. (Würde man ein Modul mit einer Klasse vergleichen, entsprächen diese Daten den Klassenvariablen.) Abbildung 8.3.: Modul-Konfigurationsdialog Im linken Teil werden alle im System registrierten Module angezeigt. Bei Auswahl eines Eintrages wird in der rechten Seite der entsprechende modulspezifische Dialog eingeblendet, falls ein solcher existiert. 8.2.2. Für einzelne Instanzen Der Nodes-Konfigurationsdialog dient dazu die Daten an den Nodes des Objektbaumes zu konfigurieren. (Wieder zum Vergleich, bei einer Klassen wären das die Instanzvariablen.) Im linken Teil ist der Objektbaum dargestellt. Wenn dort ein Element ausgewählt wird, wird die Liste im rechten oberen Teil, mit den Namen der, an gwählten Node verfügbaren, Daten gefüllt. Im rechten unteren Teil erscheint bei Auswahl eines Listeneintrages dann der Konfigurationsdialog des zuhörigen Moduls, um die Daten der ausgewählten Node konfigurieren zu können. bei nicht aktivierter Visualisierung, noch nicht durchgeführter Sensorberechnung und einem Algorithmus mit 10ms TaktZeitvorgabe. Die Unterschiede werden zum großen Teil durch die verschiedenen Thread-Implementationen der getesteten Betriebssysteme bewirkt. 47 8. Programmoberfläche Abbildung 8.4.: Nodes-Konfigurationsdialog 8.3. 3D-Ansicht Die 3D-Ansicht zeigt den Objektbaum entsprechend den Eigenschaften seiner Nodes. Das bedeutet, wenn keine Node irgendwelchen Geometriedaten enthält, bekommt man nur einen schwarzen Bildschirm zu sehen. Die 3D-Ansicht wird aller 100ms aktualisiert, vorausgesetzt es hat sich seit der letzten Aktualisierung eine Änderung in den OpenGL-Displaylisten der 3D-Objekte ergeben. Das Fenster besitzt zwei Menüpunkte, wobei „Close“ das Fenster schließt, während der Menüpunkt „New 3D View“ ein weiteres Fenster öffnet. Die Anzahl der 3D-Ansichtsfenster ist auf 3 begrenzt.2 Die Auswahlliste im oberen Bereich soll später eine Auswahl unter allen im Objektbaum definierten Ansichten/Viewpoints bieten, aber dieser Teil ist noch Arbeit, so daß derzeit nur die festvorgegebene Ansicht zur Verfügung steht. (Dasselbe bezieht sich übrigens auch auf die Lichtquellen.) 2 48 Diese Einschränkung ist vorallem Windows zu verdanken. Die Kombination aus Windows und Grafikkartentreiber setzt auf meiner Maschine folgende zwei Grenzen, eine bei ungefähr 25-28 offenen Fenstern, wobei das unterliegende GTK/GtkGlArea in einer Endlosschleife endet, und eine zweite bei 2-4 Fenstern, die sich darin äußert das nicht mehr alle Fenster aktualisiert werden, dies ist offensichtlich durch die Hardwarebeschleunigung und den Treiber bedingt. Bei Benutzung von Microsoft’s OpenGL-Software-Rendering existiert das zweite Problem nicht. 8.3. 3D-Ansicht Abbildung 8.5.: 3D-Ansichtsfenster 49 9. Simulation des Khepera 9.1. Arbeitsweise Es gibt für den Khepera im wesentlichen zwei Möglichkeiten gesteuert zu werden, die erste wäre durch ein in den Roboter geladenes Steuerungsprogramm oder über seine serielle Schnittstelle. Die Steuerung Khepera-Roboter KheperaBIOS KheperaSteuerprogramm Abbildung 9.1.: Steuerungsprogramm direkt im Roboter KheperaBIOS Serielles Interface Khepera-Roboter Serielles Kabel Steuerprogramm mit seriellem Interface Abbildung 9.2.: Steuerung des Khepera über das serielle Interface über die serielle Schnittstelle hat hierbei aber einige schwerwiegende Nachteile, denn alle Daten vom oder zum Roboter haben eine gewisse Verzögerung, die von der eingestellten Baudrate des Interfaces abhängig ist. Dadurch ist unmöglich Algorithmen zu entwickeln, die wirklich aktuelle Daten benötigen, aber auch bei normalen Anwendungen ist der Unterschied recht deutlich, wenn zum Beispiel der Roboter in die Wand rast, weil die Sensordaten nicht rechtzeitig zu Verfügung standen beziehungsweise die Stopbefehle an die Motoren zu spät kamen. Aufgrund dieser Nachteile wurde der direkte Betrieb im Roboter als die zu simulierende Betriebsart für den Simulator gewählt. Dies hat folgende Vorteile, zum ersten existiert hier mit der BIOS-Schnittstelle ein bereits fertig definiertes Interface1 , so das man auf bereits existierende Programme zurückgreifen kann2 . Als zweiten Vorteil ergibt sich im Simulator, durch das steuerbare Zeitverhalten, die Möglichkeit, die Verzögerungen eines seriellen Interfaces doch noch zu simulieren. 1 Die anderen Simulatoren Webots und Easybot benutzen ihre eigenen Interfaces, die untereinander natürlich inkompatibel sind. 2 An der Hochschule ist mir kein solches Programm bekannt, was wahrscheinlich daran liegt, daß die Interfaces des Roboters und des Simulators (Easybot) zu unterschiedlich sind, um die Programme einfach vom einen auf das andere zu übertragen. 50 9.2. Realisierung Die Aufgabe besteht nun darin, die BIOS-Aufrufe des Steueralgorithmus abzufangen und im Simulator beziehungsweise dem zuständigen Khepera-Controller-Modul abzuarbeiten. Dazu ersetzt man die normalerweise zum Programm gelinkte BIOS-Interface-Bibliothek durch eine eigene, die alle Aufrufe über eine Sprungtabelle weiterleitet. Diese Bibliothek und der eigentliche Steueralgorithmus werden dann zu einer DLL kompiliert. Dadurch hat man einerseits ein fertig übersetztes Programm, kann dieses aber jederzeit in ein anderes Programm einbinden und bei Bedarf daraus auch wieder entfernen, um zum Beispiel einen anderen Controller zu testen. Khepera Simulator-Modul KheperaBIOSSimulation Simulator SimulatorInterface DLL/Shared Object Python-C-Interface DLL-Interface KheperaBIOSUmleitung KheperaSteuerprogramm Abbildung 9.3.: Verbindung des Steuerprogrammes mit dem Simulator Neben der Anwendung dieser Vorgehensweise im Simulator gibt es aber auch andere sinnvolle Einsatzgebiete dieser Controller-DLL’s. Ein mögliches Beispiel wäre hier die Steuerung eines realen Roboters über das serielle Interface. Der Vorteil gegenüber der konventionellen Variante dieser Steuerung besteht hauptsächlich darin, daß man hier die Controller-DLL unverändert auch im Simulator probieren kann und bei geschickter Programmierung des Konverter-Programmes3 sogar bessere Reaktionszeiten erreicht, als wenn man das serielle Interface selber ansteuert. 9.2. 9.2.1. Realisierung Die BIOS-Umleitung Wie bereits erwähnt, soll es möglich sein die Original-Programme, wie sie auch direkt im Roboter arbeiten, hier unverändert zu benutzen. Dies bezieht sich natürlich nur auf die Quelltext-Ebene, denn den Prozessor und die Hardware des Roboters zu simulieren, würde leicht den Umfang einer eigenen Diplomarbeit erreichen oder gar übersteigen. Die Khepera-Programme werden für den Roboter gegen eine Bibliothek gelinkt, die dann die BIOSZugriffe realisiert. An diesem Punkt wird nun angesetzt, indem eine eigene Bibliothek (bzw. ObjektDatei) zur Verfügung gestellt wird, die nach außen hin dieselbe Schnittstelle und Funktionalität, wie die Original-Bibliothek bietet. Aber im Inneren werden alle Funktionsaufrufe auf ein von außen definierbares Ziel umgeleitet. Die Umleitung wird durch eine Struktur von Funktionspointern (eine Sprungtabelle) 3 Interne Caches für Sensorwerte (IR-Sensoren werden im Khepera nur aller 20ms abgefragt, häufigere externe Abfragen bekommen alle dasselbe Resultat.) oder Extrapolation der Werte der Positionszähler. 51 9. Simulation des Khepera Serielles Interface Khepera -InterfaceKonverter-Programm KheperaBIOSSimulation KheperaBIOS Serielles Interface Khepera-Roboter DLL/Shared Object Serielles Kabel DLL-Interface KheperaBIOSUmleitung KheperaSteuerprogramm Abbildung 9.4.: Anwendung der Schnittstelle für andere Zwecke realisiert. Neben den normalen BIOS-Aufrufen wurden noch einige eigene Funktionen hinzugefügt mit deren Hilfe der Controller-Algorithmus die Simulation mit zusätzlichen Daten versorgen kann. Diese Daten sind zum Beispiel Informationen über den Zeitverbrauch einzelner Programmteile, denn nur so wird es möglich die Simulation realistisch zu gestalten. Die Definition dieser Struktur sieht in etwa folgender maßen aus (Datei: khepera_sim.h): ... typedef struct _Khepera_BIOS Khepera_BIOS; typedef struct _Khepera_COM Khepera_COM; typedef struct _Khepera_SIM Khepera_SIM; typedef struct { #define Khepera_Interface_VERSION (0x0100) uint32 version; void *data; /* for use in simulation program */ Khepera_BIOS* BIOS; Khepera_COM* COM; Khepera_SIM* SIM; } Khepera_Interface; ... struct _Khepera_BIOS{ #define Khepera_BIOS_VERSION (0x0100) uint32 version; /* BIOS 0..15 */ /* ---------- */ 52 9.2. Realisierung void (*bios_reset) (Khepera_Interface * khepera_interface); char *(*bios_get_ident) (Khepera_Interface * khepera_interface); uint32(*bios_get_rev) (Khepera_Interface * khepera_interface); uint32(*bios_get_system) (Khepera_Interface * khepera_interface); void (*bios_restart_system) (Khepera_Interface * khepera_interface); ... Und die entsprechende Benutzung so (Datei: khepera_sim.c): ... static Khepera_Interface *__interface = NULL; ... void bios_reset(void) { if (__interface) __interface->BIOS->bios_reset(__interface); } char *bios_get_ident(void) { return (__interface) ? (__interface->BIOS->bios_get_ident(__interface)) : ("Khepera Simulator"); } uint32 bios_get_rev(void) { return (__interface) ? (__interface->BIOS->bios_get_rev(__interface)) : (0); } ... Wie man sieht ist, __interface ein Zeiger auf die Sprungtabelle. Passend dazu gibt es mit set_Khepera_Interface eine Funktion diesen zu setzen. Dieser Teil und der vom Benutzer erstellte Controller-Algorithmus werden dann zu einem funktionsfähigen Programm zusammengelinkt. Oder besser gesagt zu einer DLL, denn es soll ja in den Simulator geladen werden und dieser muß die Funktion zum Setzen der Sprungtabelle aufrufen können. Die genaue Vorgehensweise wird weiter unten bei der Besprechung des Beispiels erklärt. 9.2.2. Die Khepera-BIOS-Simulation Auf der Seite des Simulator-Moduls ist natürlich nun das passende Gegenstück notwendig, was insbesondere heißt das hier die gesamte Sprungtabelle realisiert werden muß. Zu allererst braucht man dazu Variablen, die alle Systemparameter des Roboters aufnehmen können, dazu gehören unter anderems die aktuellen Geschwindigkeiten der Räder und die aktuellen Werte der 53 9. Simulation des Khepera Abstandssensoren4 . Die Funktionen der Sprungtabelle müssen nun mit diesen Parametern den kompletten Khepera nach außen hin abbilden können, außerdem müssen sie den Simulator entsprechend steuern. Wenn zum Beispiel sich die Werte der Radgeschwindigkeit ändern sollen, muß die Bewegung bis zur aktuellen simulierten Zeit bereits ausgeführt sein5 , oder wenn neue Sensorwerte gelesen werden sollen, muß die allgemeine äußere Zeit erst auf den Stand der eigenen internen gebracht werden6 . In den simulierten Funktionen hat man weitreichende Möglichkeiten, den Zeitablauf zu beeinflussen. So kann man durch einfaches Einschieben von kurzen Pausen (simulierte Zeit) direkt vor und nach jeder Aktion eine „Simulation“ einer seriellen Verbindung zum Roboter erreichen. Wenn man dieses auch noch umschaltbar gestaltet, kann man direkt in der Simulation die Auswirkung der Verzögerungen untersuchen. (Es hat sich in der Vergangenheit gezeigt, das bei Steuerung des Khepera über das serielle Interface, die Geschwindigkeitsparameter bis um den Faktor 10 reduziert werden mußten, um den Roboter nicht mehr gegen die Wand fahren zu lassen. Grund waren die, in der Simulation nicht vorhandenen, Verzögerungen durch das serielle Interface.) Die Realisierung der Radbewegungen ist hierbei eine relativ leichte Übung. Man berechnet ausgehend von den Radgeschwindigkeiten den Mittelpunkt eines Kreises, so daß sich für beide Räder die gleiche Winkelgeschwindigkeit auf einer Kreisbahn um diesen Mittelpunkt ergibt. Mit dieser Winkelgeschwindigkeit, der Zeitdauer der Bewegung und dem Abstand des Robotermittelpunktes zum Kreismittelpunkt ergibt sich die endgültige Position des Roboters. Aus Winkelgeschwindigkeit und wieder der Zeitdauer kann man aber auch die Drehung des Roboters errechnen. Beides läst sich zu einer Transformationmatrix zusammenfassen und mit der aktuellen Matrix des Roboter verrechnen. Ausgangsvariablen sind: v0, v1 die Umfangsgeschwindigkeiten der Räder, ∆t die vergangene Zeit und d der Abstand der beiden Räder. Die Ergebnisvariablen sind: α der Winkel auf der Kreisbahn, r der Radius der Kreisbahn (des Mittelpunktes des Roboters) und ∆x, ∆z als neue Position im Koordinatensystem des Roboters ∆y = 0, denn der Roboter kann nicht fliegen. Wenn v0 = v1, ist es leicht zu berechnen: (Geradeausfahrt ohne Drehung) α = 0 ∆x = 0 ∆z = v1 · ∆t Wenn v0 <> v1, dann ergeben sich folgende Werte: v0 + v1 ·d 2(v0 − v1) v0 − v1 α = ∆t · d ∆x = r(1 − cos(α)) r = ∆z = −r sin(α) 4 Die vollständige Liste läßt sich leicht mit Hilfe der Manuals[5] für den Roboter aufstellen. Derzeit sind allerdings nur die beiden obengenannten Parameter implementiert, sozusagen als Minimalvariante. 5 Zur Erinnerung der Simulator kann in einzelnen Programmteilen bereits der allgemeinen Zeitrechnung voraus sein, solange 54 keinerlei Interaktionen mit der Umwelt erfolgen. 6 In dem man dem Scheduler mittels request_io() Bescheid gibt, das er alle anderen Prozesse auch bis zu dieser Zeitmarke laufen läßt. 9.3. Beispiel Nun läst sich die Transformationsmatrix erstellen, aus einer Rotation um die Y-Achse um den Winkel α und der Verschiebung (∆x, 0, ∆z)T 7 . Die Simulation der Abstandssensoren ist etwas komplizierter, da hierbei ein vom Sensor ausgesandter Strahl gegen jedes andere Objekt getestet werden muß, um die minimale Entfernung (das nächstliegende Objekt) herauszubekommen.8 Wenn diese minimale Entfernung dann bekannt ist, kann diese in einen Sensorwert umgerechnet werden. Um die Simulation des Sensors zu verbessern, kann man auch mehrere Strahlen verwenden und deren Resultate abhängig vom Winkel miteinander verrechnen. Bei sehr großer Anzahl der Strahlen kann man eventuell auch auf andere Verfahren ausweichen, siehe Kapitel 11.2.2, Seite 66. Derzeit ist die Sensorsimulation noch nicht vollständig implementiert, so däs hier nicht weiter darauf eingegangen werden kann. 9.2.3. Das Khepera-Controller-Modul Nachdem die unteren Ebenen abgehandelt wurden, soll nun die Benutzung dieser im Khepera-ControllerModul erklärt werden. Wenn ein neuer Khepera-Controller (DLL) in das Modul geladen wird, geschieht folgendes: • Initialisierung der Parameter des simulierten Khepera. • Laden der DLL. • Ermitteln und Überprüfen der Adressen der exportierten Funktionen. • Benutzung der Funktion set_Khepera_Interface um die eigene Sprungtabelle in der DLL zu setzen. • Registrierung der main-Funktion der DLL mit dem Scheduler des Simulators. Nach Durchführung dieser Schritte wird der Scheduler die main-Funktion starten und alle Funktionsaufrufe des Khepera-BIOS landen direkt im Khepera-Controller- Modul, welches die eigentliche Simulation realisiert. 9.3. Beispiel Um zu demonstrieren wie man eine Controller-DLL für den Simulator in der Praxis herstellt, hier nun ein Beispiel. 9.3.1. Quelltext Zuerst einmal der Quelltext eines normalerweise im Khepera laufenden Programmes 9 . 7 Wie die Matrix erstellt wird, steht in jedem besseren 3D-Buch, siehe auch [1]. Variante dies zu optimieren besteht in der Benutzung von Bounding Boxes. Wenn ein Strahl eine Bounding Box nicht trifft, kann auch kein darin enthaltenes Objekts getroffen werden, dadurch können bestimmte Teile des Objektbaumes von vornherein ausgeschlossen werden. 9 Das Programm entstammt den Beispielen zum Khepera. Es wurde für den Abdruck etwas gekürzt. 8 Eine 55 9. Simulation des Khepera 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 56 /* ; Braitenberg test ; ================ */ #include <khesys.h> #include <stdlib.h> #include <stdio.h> #define #define #define #define KNBSENSORS KNBMOTORS KSCALING KSPEED 8 2 400 20 /* * MAIN * ==== * * This process implements the Braitenberg vehicle 3 algorithm. * */ void main(void) { int32 status; IRSENSOR *sensor; uint32 sensBuf[KNBSENSORS]; int i, s, m; int32 potential[KNBMOTORS], speed[KNBMOTORS]; int32 matrix[KNBMOTORS][KNBSENSORS] = { {-5, -15, -18, 6, 4, 4, 3, 5}, {4, 4, 6, -18, -15, -5, 5, 3}}; com_reset(); var_reset(); sens_reset(); str_reset(); mot_reset(); mot_config_speed_1m(0, 3800, 800, 100); mot_config_speed_1m(1, 3800, 800, 100); sensor = sens_get_pointer(); /* Calculate the neuron (potentials x Gain) and add. the initial KSPEED */ for (;;) { for (i = 0; i < KNBSENSORS; i++) sensBuf[i] = sensor->oProximitySensor[i]; for (m = 0; m < KNBMOTORS; m++) { potential[m] = 0; for (s = 0; s < KNBSENSORS; s++) { potential[m] += (matrix[m][s] * (int32) sensBuf[s]); } 9.3. Beispiel 54 55 56 57 58 59 60 61 62 63 64 65 speed[m] = (potential[m] / KSCALING) + KSPEED; } #ifdef SIMULATION /* time for one loop */ sim_checkpoint(0.01); /* 10ms */ /* 10 ms is unrealistic for this small piece of code, but in a real Khepera sensor values are updated only every 20ms, so we are still better than reality */ #endif mot_new_speed_2m(speed[0], speed[1]); } } Dieses Programm realisiert einen einfachen Ausweichmechanismus, der wenn auf einer Seite sich ein Hindernis10 befindet das gegenüberliegende Rad abbremst. Ohne Hindernis fährt der Roboter geradeaus. Die einzige Ergänzung ist der Extra-Abschnitt zur Kommunikation mit dem Simulator. Über diese Anweisung wird dem Simulator die Zeitdauer des Verarbeitungsvorganges bekannt gemacht. 9.3.2. Erstellen der Controller-DLL Wenn man seinen Algorithmus fertig programmiert hat, kann man dann die DLL für den Simulator erstellen. Manuell mit gcc Wenn man unter Linux arbeitet, ist das noch relativ einfach von Hand zu erledigen: • Übersetzung der Objektdateien der Steuerung. gcc -fpic -c braiten.c • Übersetzung des Ersatzes für die Khepera-BIOS-Bibliothek. gcc -fpic -c khepera_sim.c • Zusammenlinken aller Teile zur DLL oder hier besser gesagt zum Shared Object. gcc -shared -o braiten.so braiten.o khepera_sim.o Unter Windows, aber auch mit einigen Unix/Posix-Systemen (AIX, BeOS), ist das Erzeugen solcher DLL’s komplizierter, einerseits wegen der notwendigen Spezifikation der zu exportierenden Symbole und unter Windows wegen der Vielzahl unterschiedlicher Compiler, die alle andere Kommandozeilenparameter erwarten. Automatisch mit build_dll Die bereits erwähnten Probleme bei der DLL-Erstellung machen es sinnvoll das irgendwie zu vereinfachen. 10 Hindernisse zeigen sich durch hohe Sensorwerte. 57 9. Simulation des Khepera Hier bietet es sich an distutils11 zu benutzen. Distutils hat eine interne Compiler-Abstraktion, die hier ausgenutzt wird. Mit Hilfe dieser Compiler-Abstraktion wurde das Python-Programm build_dll.py (siehe Anhang D Seite 93) erstellt, welches den kompletten Prozess zur Erstellung einer DLL unabhängig vom System ausführt. Der zu benutzende Compiler und dessen notwendigen Parameter werden dazu dem Python-eigenen Makefile entnommen. Für Windows ist der Microsoft Visual C++ Compiler der Standard, man kann aber auch andere einstellen. Derzeit sind das Borland C++ 5.5 und die GNU-CCompiler mingw32 und cygwin.12 Letztendlich hat man nur noch folgendes kleines Programm auszuführen und fertig ist die DLL (oder das Shared Object.) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 9.3.3. """ Example script to build a Khepera Controller DLL If you need more control look at build_dll.py """ from RSTk.tools.build_dll import * build_dll( #compiler="mingw32" build_temp = ".", # files of which your controller consists sources = ["braiten.c","khepera_sim.c"], # always include the path to the Khepera header files include_dirs = [".","./include/RSTk/khepera"], # what is the name of the resulting dll without extension output_name = "braiten", # these symbols are mandatory export_symbols = ["main","set_Khepera_Interface","get_Khepera_Interface"], libraries = [], library_dirs = ["."], ) Test Wichtig: Der folgende Text beschreibt einen Vorgang, der derzeit nicht zum beschriebenen Resultat führt, weil das Controller-Modul noch nicht fertig implementiert ist. Nachdem man nun eine fertige DLL hat, kann man sie testen. Dazu lädt man im Simulator die Beispieldatei und öffnet dann den Nodes-Konfigurationsdialog. Nach Anwählen des Objekts „Khepera“ im linken Teil, sollte die Seite „Khepera.controller“ zur Auswahl stehen. Dort kann man nach Aktivierung der „Load other controller“-Schaltfläche, die eben erstellte DLL auswählen. Diese wird damit geladen und, wenn alles ok ist, beim Scheduler registriert. Wenn man nun die Simulation laufen läßt, sollte der Roboter den Wänden ausweichen. Der einfache Steueralgorithmus hat übrigens ein Problem, Wenn der Khepera zum Beispiel in eine Ecke fährt und dort von beiden Seiten symmetrisch hohe Sensorwerte bekommt, werden auch beide Räder auf die Geschwindigkeit Null gesetzt, und der Roboter „steckt“ fest. 11 Dies 12 58 ist das Python-Paket, das auch das Gesamtsystem übersetzt und installiert. Andere Compiler könnten notfalls jederzeit in distutils integriert werden, über das wie kann ich jederzeit Auskunft geben, immerhin sind die GNU-C Anpassungen bereits von mir. 9.3. Beispiel Abbildung 9.6.: Konfigurationsdialog des Khepera-Controller-Moduls 59 10. Aktueller Entwicklungsstand Der Simulator ist derzeit als im Prototypenstatus befindlich zu verstehen, aufgrund dessen funktionieren einige Dinge noch nicht oder nicht so, wie sie sollten. 10.1. Stand der Entwicklung Die grundlegenden Programmstrukturen vor allem zur Realisierung des Objektbaumes und der Integration anderer Module sind im wesentlichen fertig. Einige dieser Programmteile müssten allerdings noch einmal durchgesehen werden, insbesondere um Fehler bei der Referenzzählung auszuschliessen1 . Weiterhin sollte man diese Gelegenheit nutzen und die Module um Dokumentationsstrings für Python erweitern, dadurch könnten dann die kompletten Moduldokumentationen automatisch erstellt werden, außerdem gibt einige Modul-Browser, die darauf beruhen. Neben diesen internen Dingen, sind für viele der RSTk-Module die Konfigurationsdialoge derzeit noch nicht implementiert oder lassen keine Veränderungen der Werte zu. Weiterhin sind einige vorgesehene Module noch nicht fertig implementiert, dies betrifft vor allem die Sensorsimulation des Khepera. Die Quelltexte liegen zwar zum Teil schon vor, wurden aber noch nicht integriert. Ein weiteres noch zu implementierendes Detail ist eine Realisierung eines High-Level-Interfaces, welches die grundlegenden Dinge wie zum Beispiel das Laden von Dateien vereinfacht. Im derzeitigen Zustand hat ein Script hier noch zu viel mit Modulen auf niederer Ebene zu tun. 10.2. Portabilität Der Simulator wurde erfolgreich mit Linux und FreeBSD 3.3 getestet. Auf AIX läuft der Simulator im Scriptbetrieb einwandfrei, die grafische Oberfläche hingegen konnte noch nicht getestet werden, da die Python-Erweiterungen pygtk und PyOpenGL dort noch nicht funktionieren. Dies hängt wiederum damit zusammen, daß aus irgendeinem bisher unbekannten Grund sich GTK und Mesa nur als statische Bibliotheken übersetzen lassen. Auf Windows läuft der Simulator ohne Probleme, wenn man von dem weiter unten erwähnten Problem des GTK mit NT absieht. Ein kurzer Test unter BeOS zeigte, daß die meisten Module ohne weiteres zu kompilieren sind. Das Problem liegt hier in der nicht vorhandenen Anpassung an die BeOS-Threads, die etwas anders zu programieren sind als die sonst im Simulator verwendete pthreads-Bibliothek. 1 Dabei dürften die Dokumentationen der nächsten Python-Version gute Dienste leisten, da hier erstmals zu jeder einzelnen Python-Funktion aufgeführt wird, ob diese den Referenzzähler eines Objektes verändert oder nicht. Diese Dokumentation findet man auf der Python-Webseite[40], wenn man dort nach den Entwicklerversionen sucht. 60 10.3. 10.3. Bekannte Probleme Bekannte Probleme Fast alle bekannte Probleme sind irgendwie auf die pygtk-Python-Erweiterung und dessen Arbeit mit GTK zurückzuführen. 10.3.1. pygtk und GTK • Das wahrscheinlich geringste Problem besteht darin, daß die Farbeinstellung zum Zeichnen von GTK-Elementen in einigen Konfigurationsdialogen nicht funktioniert. Hierbei ist aber nicht klar, wo das Problem eigentlich liegt, beim GTK oder bei pygtk. Der Effekt ist der folgende, auf einigen Rechnern werden die jeweils korrekten Farben angezeigt, während auf anderen Rechnern falsche angezeigt werden. Allerdings bezieht sich das nur auf die Anzeige durch das GTK, OpenGL zeigt auf jeden Fall die korrekte Farbe an. • Die pygtk-Versionen vor Version 0.6.6 setzen beim Starten die Locale-Einstellungen des Programm mittels GTK. Auf deutschen Rechnern bedeutet dies unter anderem das neben deutschen Beschriftungen in verschiedenen GTK-Dialogen auch das Verhalten verschiedener C-Funktionen verändert wird. Dies betrifft insbesondere auch die Konvertierung von Zahlen, die dann statt eines Dezimalpunktes mit dem deutschen Dezimalkomma geschrieben sein müssen. Das Problem besteht nun darin, daß Python beim ersten Einlesen seiner Module darauf angewiesen ist, daß die Zahlenkonvertierungen nach dem Standard-C-Schema erfolgen. Wenn diese aber auf Deutsch umgeschaltet wurden, werden alle Nachkommastellen im Programm ignoriert, was zu teilweise merkwürdigen Effekten führt. Derselbe Effekt stört auch das Einlesen entsprechender Zahlen aus den XML-Dateien. Abhilfe schafft hier das Löschen der entsprechenden Zeile aus der Datei gtk.py der pygtk-Moduls, das Entfernen der Sprachkennung aus den Umgebungsvariablen2 oder die Installation einer neueren pygtk-Version. • Die Windows-Version von GTK in Verbindung mit pygtk führt unter Windows NT zu einem Deadlock. Abhilfe schafft hier nur eine Änderung im Code von pygtk selbst, wobei man verhindert das die Kontrolle über den Python-Interpreter abgegeben wird. Dies hat allerdings den Nachteil, daß eventuell andere laufende Threads warten müssen, bis ein Event-Handler ausgeführt wird. In dem dann ausgeführten Python-Code gibt Python selbst regelmässig die Kontrolle kurz ab, und läßt so andere Threads zum Zuge kommen. Dieses Verhalten kann man, an der, auf der CD befindlichen, NT-Variante sehr gut sehen, wobei entweder der Simulationsthread oder die Benutzeroberfläche stark behindert werden. Der einzige Grund, daß die Simulation dort überhaupt noch eingermaßer läuft, ist die regelmäßige Aktualisierung der Anzeige, die in einem Event-Handler durch Python ausgeführt wird, wo dann die anderen Threads die Gelegenheit zur Übernahme des Python-Interpreters erhalten. • Unter UNIX gibt es ein weiteres Problem, welches unter Windows nicht auftritt. Wenn man beim Start ein 3D-Fenster öffnet, dieses wieder schließt, dann eine Datei lädt und wieder ein 3D-Fenster öffnet, stürzt das System bei dieser letzten Aktion ab. Im Debugger zeigte sich, daß dieses Problem offensichtlich irgendwo im Zusammenspiel zwischen pygtk und GTK zu suchen ist3 . Da dieses Problem unter Windows nicht auftritt, könnte es möglicherweise auch ein Fehler im GTK selbst 2 Unter 3 UNIX: unset LANG Wenn man sich den Backtrace im Debugger anzeigen läßt, zeigt dieser über 60 Stackframes, die mehrfach zwischen Python und GTK-Event-Handlern wechseln. 61 10. Entwicklungsstand sein. (Die Windows-Version des GTK beruht auf aktuellerem Quellcode (Entwickler-Version 1.3) als die UNIX-Version (1.2.6).) 62 Teil III. Erweiterungen 63 11. Weiterer Ausbau 11.1. Notwendige Erweiterungen Neben der weiteren Vervollständigung der, bisher nur teilweise implementierten, bereits vorhandenen Module, gibt es auch einige wichtige Erweiterungen, die unbedingt notwendig sind um das Endziel des Allround-Simulators zu erreichen. 11.1.1. Graphischer Szeneneditor Dies ist wahrscheinlich die dringendste Erweiterung. Derzeit gibt es zwar die Möglichkeit einzelne Elemente über den Nodes-Konfigurationsdialog zu verändern, aber bei größeren Welten muß noch mindestens die Möglichkeit bestehen, ein Element durch Anklicken in einem der OpenGL-Fenster auszuwählen. Weiterhin gibt es derzeit keine Möglichkeit neue Objekte einzufügen oder ähnliche Manipulationen am Objekt-Baum durchzuführen auch dazu wäre ein graphischer Szeneneditor nützlich. Zumindest braucht man aber ein Konvertertool, welches zum Beispiel VRML[51] in unser Format konvertieren kann. Dadurch könnte man wenigstens die 3D-Umgebung in einem entsprechenden Programm (3D-Studio, ...) erstellen. Dann muß man aber auch die anderen VRML-Elemente (wie zum Beispiel IndexedFaceSet) als Geometry-Module im Simulator nachrüsten, aber das ist eigentlich keine komplizierte Sache. 11.1.2. Kollisionserkennung Ein weiteres Manko des derzeitigen System ist, daß keinerlei Kollisionskontrollen stattfinden, so das Roboter zum Beispiel durch Wände fahren können. Es gibt da eine sehr interessante Programmbibliothek im Internet, die genau dies für uns realisieren könnte. Diese Bibliothek namens SOLID[3] ist bezüglich der Spezifikation der Geometriedaten der Körper an VRML[51] angelehnt und würde somit perfekt zu unserem Modell passen. SOLID selbst unterliegt der LGPL und ist damit uneingeschränkt für unsere Zwecke nutzbar. 11.1.3. Dynamiksimulation Die nächste wünschenswerte Ausbaustufe wäre dann die Integration einer Dynamiksimulation wie zum Beispiel DynaMo[2]. Diese Bibliothek ist an derselben Universität wie SOLID entstanden, so daß zu erwarten ist, daß beide perfekt zusammenpassen. (In der Tat verweist die DynaMo-Webseite direkt zu der von SOLID.) 65 11. Weiterer Ausbau Mit Hilfe der Dynamiksimulation sind dann ganz andere Arten der Simulation machbar. Zum Beispiel kann ein Khepera-Roboter direkt über ein Drehmoment an den Rädern bewegt werden, inklusive irgendwelcher Schlupfeffekte zwischen Rad und Boden (, dies setzt voraus das man die maximale Kraftübertragung passend festlegt.) Als weiteres Beispiel sei hier die Möglichkeit genannt, andere Gegenstände in Bewegung zu versetzen und damit ein Roboterfussballturnier zu simulieren. Und zu guter letzt könnte man Roboter mit Beinen simulieren, die dann natürlich erstmal darauf programmiert werden müssten, aufrecht zu stehen ohne umzukippen. 11.2. Weitere Vorschläge für Erweiterungsmodule Die nachfolgenden Vorschläge sollen hier als Beispiele für eigene Sensoren dienen. 11.2.1. GPS Ein GPS1 -Sensor ist der wohl am einfachsten zu realisierende Sensor. Wenn man sich die Transformationsmatrix bezüglich der Weltkoordinaten vom System geben läßt, sind darin sowohl die Koordinaten im Raum als auch die Kompassdaten bezüglich der Ausrichtung enthalten. Einfacher kann man eigentlich keinen neuen Sensortyp haben. 11.2.2. 3D-Scanner Ein 3D-Scanner liefert eine zweidimensionale Abbildung der Umwelt bei der einzelne Bildpunkte den Abständen zum jeweils nächstliegenden Objekt entsprechen. Das Bild ist also ein Tiefenbild. Man kann so etwas durch eine Menge ausgesendeter Suchstrahlen simulieren, aber es gibt auch andere Wege dieses Bild zu erhalten. Die meisten 3D-Systeme heutzutage arbeiten intern mit einem Z-Buffer-Algorithmus3 . Nach der kompletten Ausgabe der 3D-Daten enthält der Z-Buffer die Z-Koordinaten der Bildpunkte. Wenn man also den Z-Buffer ausliest, erhält man ein Tiefenbild. Man muß vorher nur die Kamera an die richtige Stelle setzen und hinterher die Projektion der Z-Koordinaten zurückrechnen, und dies geht wahrscheinlich viel schneller als alle Bildpunkte einzeln durch Suchstrahlen zu erfassen. Leider ist diese Vorgehensweise im Simulator nicht ohne weiteres anwendbar, da dieser auch ohne Oberfläche und damit auch ohne OpenGL-Ausgabe arbeiten kann. Es müsste hier also eine eigene Lösung gesucht werden. (Man könnte es mit Off-Screen-Rendering versuchen.) 11.3. Abbildung 11.1.: Inhalt des Z-Buffers nach dem Rendern 2 Scriptgesteuerte Anwendungen Hier sollen nun einige mögliche Anwendungen der Scriptsteuerung erläutert werden. Bei den hier erläuterten Anwendungsmöglichkeiten sollte man sich immer bewusst sein, das Python eine vollständige Programmiersprache darstellt und so auch sehr komplexe Aufgaben ausführen kann. Wenn es denn sein muß, könnte man die Simulation über einen in Python realisierten Webserver steuern. (Zur Ausgabe der Bilder würde dann Off-Screen-Rendering zur Anwendung kommen.) 1 Global Positioning System - Die System ermöglicht jederzeit die Feststellung des eigenen Standortes auf dem Planeten, Längengrad, Breitengrad und Höhe. 2 Je dunkler der Bildpunkt, desto näher war des entsprechende Objekt der „Kamera“. 3 Zu jedem Bildpunkt wird die Tiefeninformation im Z-Buffer abgelegt. Damit läßt sich dann ermitteln, ob andere 3DElemente, die die selben 2D-Koordinaten ergeben, davor oder dahinter liegen. 66 11.3. Scriptgesteuerte Anwendungen Die meisten der vorgeschlagenen Anwendungen gehen auf eigene Versuche mit dem Simulator Easybot zurück, wobei genau diese Anwendungen nicht machbar waren. Man sehe sich hierzu auch die entsprechende Beschreibung in Kapitel 1 an. 11.3.1. Hintergrundbetrieb selbstlernender Controller Wenn man einen selbstlernenden Controller (Neuronales Netz o.ä.) trainiert, braucht dieser meistens einige Zeit bevor sich brauchbare Ergebnisse einstellen. Außerdem muß irgendwie die Qualität des Controllers festgestellt werden, entweder durch den Controller selbst oder durch Überwachung des gesteuerten Objekts. Somit ergeben sich nun zwei Ansatzpunkte für eine Scriptsteuerung, erstens die Steuerung des System über eine längere Zeit im Hintergrundbetrieb und die Überwachung der Controllerqualität. Wenn sich brauchbare Ergebnisse eingestellt haben, kann das Script dann die Resultate und Parameter speichern und die Simulation beenden. Zur Überwachung gibt es mehrere Möglichkeiten, man führt die Simulation für bestimmte Zeit (1s, 1min, ...) aus und überprüft dann den Zustand oder der Controller überwacht sich selbst und signalisiert über den Callback-Mechanismus alle relevanten Veränderungen an das Script, welches dann entsprechend reagiert. 11.3.2. Genetische Algorithmen Mit Hilfe der Scriptsteuerung lassen sich auch normale Steuerungen durch einen genetischen Algorithmus optimieren, die einzige Voraussetzung besteht darin, daß diese Steuerung einen veränderbaren Parametersatz besitzen muß. Das Script hat folgende Schritte zu realisieren: 1. Erzeugen einiger zufälliger Parametersätze. 2. Laden bzw. Rücksetzen der Umgebung. 3. Laden eines Parametersatzes in die Steuerung. 4. Ausführen der Simulation für eine bestimmte Zeit oder bis zum Auftreten eines bestimmten Ereignisses, zum Beispiel der Kollision des Roboters mit einer Wand. 5. Ermittlung des Fitnesswertes und Speicherung desselben. 6. Solange noch ungetestete Parametersätze vorhanden sind, weiter bei 2. 7. Berechnung der Nachfolgegeneration von Parametersätzen, entsprechend den Fitnesswerten der aktuellen Generation. 8. Fortsetzung bei Schritt 2, solange das Ergebniss bzw. die Fitnesswerte nicht gut genug sind. 9. Abspeichern der besten Parametersätze und beenden der Simulation. Im Simulator Easybot mussten genetische Algorithmen noch mit dem Steuerungsalgorithmus zusammen programmiert werden, über die vorgestellte Lösung kann man die Steuerung und den genetischen Algorithmus vollstandig voneinander trennen. 67 11. Weiterer Ausbau 11.3.3. Vergleich verschiedener Steueralgorithmen oder Umgebungen Meist werden Steuerungen in einer festgelegten Umgebung trainiert. In der Simulation ist es möglich diese beliebig zu verändern, entweder durch das Laden verschiedener vorgefertigter Umgebungen oder durch zufallsgesteuerten Aufbau einer Umgebung direkt durch das Script im Simulator. Durch diese Art von Abwechslung sollten sich viel robustere Steuerungen trainieren lassen. Eine weitere Anwendung in dieser Richtung besteht in der Evaluierung von Steuerungsalgorithmen. Zum Beispiel könnte man für einen Labyrinth- Algorithmus testen, welche Gangbreiten nicht unterschritten werden dürfen, damit der Gang noch benutzt wird. Durch Beeinflussung einzelner Sensoren bestünde außerdem die Möglichkeit der Erfassung von Reaktionen auf Sensordefekte und ähnliches. 11.3.4. Turnierbetrieb Zuletzt soll hier noch die Variante des Konkurrenzverhaltens mehrerer Roboter betrachtet werden. Das wären zum Beispiel das allseits beliebte Roboterfussball, aber auch die Art Überlebenswettkampf wie er zum Beispiel beim ALife-Contest4 [12] ausgetragen wird. Gemeinsam ist beiden Varianten, daß eigentlich jeder gegen jeden getestet werden sollte, um eindeutig eine Rangfolge festlegen zu können. Da aber die Anzahl der Simulationsläufe quadratisch mit der Anzahl der Teilnehmer zunimmt, ist das der ideale Kandidat für eine automatisierte Durchführung und Betrieb als Hintergrundjob auf einem leistungsstarken Server. Die Realisierung als Script ist hierbei relativ einfach: 1. Einlesen der Daten der zu testenden Controller (aus einer Datei). 2. Erzeugen aller Kombinationen in einer Liste. 3. Abarbeiten der Liste mit folgenden Schritten für jeden Eintrag: a) Laden/Rücksetzen der Umgebung. b) Initialisieren der beiden Kontrahenten. c) Ausführen der Simulation für eine gewisse Zeit oder bis ein anderes Abbruchkriterium auftritt. d) Speichern der Resultate. 4. Aus den gewonnenen Ergebnissen läßt sich nun eine Matrix erstellen oder die Rangordnung ermitteln. 4 Zwei Roboter befinden sich in einer Umgebung mit verschiedenen Versorgungspunkten (z.B. Energie). Die Energie der Roboter nimmt mit der Zeit ab und kann an den Versorgungspunkten nachgeladen werden. Diese brauchen aber immer längere Zeit um selbst reaktiviert zu werden. Am Ende wird ein Roboter „verhungern“. 68 12. Erstellung eines Erweiterungsmoduls Da keine ausführliche Beschreibung der Modulinterfaces im Rahmen dieser Arbeit machbar ist, soll hier die wesentliche Vorgehensweise zur Erweiterung an einem Beispiel erklärt werden. Allerdings müssen auch hier Einschränkungen gemacht werden, da die komplette Beschreibung um die 50 Seiten einnehmen würde. 12.1. Das Tracker-Modul 12.1.1. Aufgabe Das Modul soll die Position des zugehörigen Objektes im 3D-Raum aufzeichnen und bei Bedarf als Datei abspeichern, beziehungsweise die Daten auf dem Bildschirm darstellen können. Die aufgezeichneten Daten sollen die Position in Weltkoordinaten und den Zeitpunkt enthalten. Es sollen folgende Dinge konfigurierbar sein: • ob überhaupt Daten aufgezeichnet werden, • wie lange zurück sollen diese gespeichert bleiben„ • ob diese Daten in der Anzeige mit angezeichnet werden (als Spur) und • in welcher Farbe. 12.1.2. Vorgehensweise Das Modul muß alle Positionsänderungen des zugehörigen Objektes aufzeichnen, dazu ist es notwendig bei jeder potentiellen Veränderung die Positionsdaten zu überprüfen. Der Simulationsscheduler besitzt in ein Callback-Objekt über welches er die Aktualisierung der Welt veranlaßt („update_world“) beziehungsweise deren Abschluß bekannt gibt („update_world2“). An dieser Stelle wird nun angesetzt und ein eigener Handler installiert, der die aktuelle Transformationsmatrix in Bezug zu den Weltkoordinaten anfordert und mit der aktuellen Zeit zusammen in einer Liste abspeichert. Das Modul muß sich also an einen Callback anhängen und einige Matrix- und Listenoperationen durchführen, aus diesem Grund sollte dieser Teil des Moduls in C geschrieben sein, um die Geschwindigkeit nicht all zu sehr zu beeinflussen. (Jeder Wechsel nach Python erfordert eine Synchronisation mit dem Hauptthread, und dies benötigt einige Zeit.) Das Modulinterface, der Konfigurationteil und das Abspeichern der Daten in eine Datei sind weniger zeitkritisch, so daß diese Teile in Python realisiert werden. 69 12. Erstellung eines Erweiterungsmoduls Als dritter Teil ist noch das Anzeigen der Spur auf dem Bildschirm zu betrachten . Dies könnte theoretisch auch von Python aus realisiert werden, wird aber der Geschwindigkeit halber auch in C programmiert. Da die Spur nicht als reguläres Objekt existiert, müssen wir es selbst zeichnen und hängen die entsprechende Routine deshalb an den „draw_userobjects“-Callback-Slot des OpenGL-Moduls (View_3D.opengl). 12.2. Implementierung Der Quelltext findet sich in leicht verkürzter Form in Anhang. Das reale Modul enthält noch mehr Quellcode, der derzeit aber noch nicht benutzt wird1 . Die folgende Beschreibung ist nur eine Kurzbeschreibung einiger Details, für weitergehende Informationen sehe man sich den Quelltext und die recht ausführliche Python Dokumentation[38] zum Thema Python-Erweiterungen an. 12.2.1. Python-Objekte in C Zuerst muß wie für alle in C-geschriebenen Python-Objekte, dieses als entsprechende Struktur angelegt werden. struct _TrackerObject { PyObject_HEAD PyObject * node; BOOLEAN record; BOOLEAN record_all; double time; BOOLEAN show; vector4d color; struct _pos_time ring_list; int list_count; long update_world2_id; long draw_userobjects_id; }; /* /* /* /* /* /* /* /* /* /* /* Python intern */ welches Objekt wird ueberwacht */ aufzeichnen Ja/Nein */ ohne Beschraenkung */ Zeitbeschraenkung */ Spur anzeigen */ in welcher Farbe */ Liste der "Mess"-werte */ Anzahl der Listeneintraege */ ID fuer Scheduler-Callback-Funktion */ ID fuer OpenGL-Callback-Funktion */ Der mit PyObject_HEAD bezeichnete Teil ist hierbei, der von Python beanspruchte Teil, in dem zum Beispiel der Referenzzähler und die Typkennzeichnung untergebracht sind. Die restlichen Felder dienen unseren eigenen Zwecken. Die Typkennzeichnung in Python ist eigentlich die Adresse einer Struktur, die Funktionspointer für alle möglichen Operationen mit Python-Objekten enthält. Für unser Beispiel sieht sie wie folgt aus: /* type definition of our Tracker static PyTypeObject TrackerObject_Type = { PyObject_HEAD_INIT(NULL) 0, "Tracker", sizeof(TrackerObject), 0, /* methods */ 1 Das object */ /*ob_size */ /*tp_name */ /*tp_size */ /*tp_itemsize */ betrifft hauptsächlich das C-Interface des Moduls, da aber bisher nur über Python auf das Modul zugegriffen wird, ist das hier nicht von Interesse. 70 12.2. (destructor)__tracker_dealloc, 0, (getattrfunc)__tracker_getattr, (setattrfunc)__tracker_setattr, 0, (reprfunc)__tracker_repr, 0, 0, 0, 0 }; Implementierung /*tp_dealloc */ /*tp_print */ /*tp_getattr */ /*tp_setattr */ /*tp_compare */ /*tp_repr */ /*tp_as_number */ /*tp_as_sequence */ /*tp_as_mapping */ /*tp_hash */ Wie man sieht man sieht, sind einige der Felder belegt. Dies sind die Funktionen, die das Tracker-Objekt selbst implementieren soll. Die anderen Funktionen bzw. Funktionsklassen stehen für dieses Objekt nicht zur Verfügung, zum Beispiel sind keine Interfaces als Liste (sequence) oder Verzeichnis (mapping) vorhanden. Die genaue Implementation entnehme man dem Quelltext im Anhang D, Seite 95. Als Details wären hierzu zu erwähnen, das beim Anlegen des Objektes auch einige Funktionen bei den Callbacks des Scheduler und OpenGL-Moduls registriert werden. Über diese Funktionen läuft dann die eigentliche Funktionalität des Objektes. Das zweite Detail betrifft das Feld node, welches die Adresse der zugeordneten Node enthält. Dieses Feld wird nicht direkt gesetzt, sondern es wird der zentrale Callback des Nodes-Moduls angezapft und wenn irgendwann einer Node ein Tracker-Objekt zugeordnet wird, wird dessen node-Feld entsprechend gesetzt2 . Nach dem Objekt-spezifischen Teilen fehlt eigentlich nur noch die Initialisierungsfunktion des Moduls. Diese muß den Namen initModulename haben, damit Python sie beim Laden des Moduls findet. Die wichtigste Aufgabe ist hier die Registrierung des Moduls und dessen Funktionen bei Python (Py_InitModule), wird diese vergessen kann das Modul nicht funktionieren. Weiterhin werden in dieser Funktion die statischen Moduldaten initialisiert. Das sind für dieses Modul einerseits die Referenzen auf die Strings, welche die Callback-Slots bezeichnen, und andererseits werden hier die CallbackHandler registriert, die sich um die Node-Referenzen der einzelnen Objekte kümmern. 12.2.2. Zeitkritische Teile Als zeitkritisch muß man in diesem Modul besonders zwei Funktionen bezeichnen, einerseits die Ermittlung der Positionsdaten und deren Abspeichern in einer Liste und andererseits das Zeichnen dieser Daten mittels OpenGL. Insbesondere die erste der beide Funktionen wird unter Umständen im Millisekunden-Takt aufgerufen, so daß es einen extremen Bremseffekt zur Folge hätte, dies in Python zu realisieren3 . Bei den OpenGLBefehlen ist das nicht ganz so extrem, aber warum sollte man dort zu Python wechseln, nur um von dort wieder nach C zu wechseln um die OpenGL-Befehle abzusetzen. Die Realisierung dieser beiden Funktionen ist einfachste C-Programmierung, so das hier auf dem Quelltext im Anhang D, Seite 95 verwiesen werden soll. 2 Es 3 gibt nur diesen Weg, weil beliebige Objekte an die Nodes angehängt werden können, und die haben evt. keine Referenz nach oben, deshalb kann das Nodes-Modul dies nicht erledigen. Beide Funktionen werden über Callbacks aufgerufen, die auch von Python aus benutzbar sind. Allerdings müßte dazu jedesmal erst mal der Python-Interpreter angefordert werden, was einige Zeit dauern kann, wenn dieser gerade durch einen anderen Thread benutzt wird. 71 12. Erstellung eines Erweiterungsmoduls 12.2.3. Modulinterface in Python Jetzt fehlt nur das Modulinterface und die Oberfläche des Konfigurationsdialoges. Jedes Modul, das irgendwie Daten speichern oder konfigurierbar sein will, muß eine Klasse, die von _module_template4 abgeleitet wurde, instantiieren und im zentralen Repository anmelden. Über dieses Objekt werden dann die Funktionen zum Laden und Speichern oder auch die Anforderung eines Konfigurationsdialoges realisiert. Die Realisierung der XML-Lade- und -Speicher-Funktionen ist hier eigentlich nicht der Rede wert, denn sie entspricht einer 1:1 Umsetzung des bereits früher erwähnten Funktionsprinzips, deshalb soll hier auf den Quelltext Anhang D, Seite 103 verwiesen werden. Etwas anders sieht das bei der Oberfläche des Konfigurationsdialoges aus. Denn hier gibt es eigentlich nichts über dessen Programmierung zu sagen, da er nicht programmiert wurde, sondern mittels glade[19] einfach zusammengebaut wurde. Dann wurde mittels: python glc.py *.glade gui.tmp die Beschreibungsdatei in ein fertiges Python-Programm umgewandelt. Da aber für den Konfigurationsdialog keine Toplevel-Fenster nutzbar sind, wurden diese mittels der folgenden Anweisung durch Nicht-Toplevel-Fenster (eine GTK-Eventbox) ausgetauscht: python no_top.py <gui.tmp >gui.py Während des oben beschriebenen Vorgangs entsteht noch eine weitere Datei die *Handlers.py heißt, wobei der Stern für den Namen des, in Glade angelegten, Fensters steht (im Beispiel ConfigHandlers.py.) In dieser Datei sind bereits Funktionsprototypen für alle Signal-Handler (Schaltfläche gedrückt, Maus bewegt, ...) angelegt, diese müssen dann nur noch fertig programmiert werden. Auch dieses soll hier nicht weiter ausgeführt werden, sondern an dieser Stelle auf die Quelltexte (auf CD) und die Dokumentationen von pygtk[4] und GTK[22]verwiesen werden. 12.3. Installation Nachdem alle Quelltexte nun vorhanden sind, müssen diese nur noch in das Setup eingebunden werden. Normalerweise würde man ein eigenes Setupscript schreiben, aber hier wird alles mit in das zentrale Setupscript für das gesamte System eingebunden5 . Dazu werden einerseits die folgenden Zeilen: ’RSTk.modules.Examples’, ’RSTk.modules.Examples.tracker_gui’, ’RSTk.modules.Examples.tracker_gui.instance_config’, für den Python-Teil des Moduls in die packages-Liste eingetragen und für den Teil in C ist die ext_modules-Liste um folgenden Eintrag zu ergänzen: 4 Diese 5 72 Basisklasse kann aus dem Python-Modul RSTk.utils.repository importiert werden. Wenn man alle nicht zu diesem Modul gehörigen Einträge aus dem zentralen Setupscript entfernen würde, hätte man das Grundgerüst für ein eigenes, man müßte nur noch die Namen ändern. Eine ausführlichere Erklärung, wie solche Setupscripte zu schreiben sind, findet man in der Dokumentation zu distutils.[16] 12.4. Ergebnis Extension("modules.Examples._tracker", ["src/RSTk/modules/Examples/_tracker.c"], include_dirs = opengl_include_dirs, library_dirs = opengl_library_dirs, libraries = opengl_libraries ), Nachdem dies getan ist, muß es noch übersetzt und installiert werden6 . Dazu dienen wieder folgende Anweisungen: python setup.py build python setup.py install (Wenn irgendwelche anderen Parameter bei der ersten Installation des Systems angegeben wurden, so müssen diese auch hier angegeben werden.) Danach sollte alles fertig installiert sein. 12.4. Ergebnis Nach Einfügen des Moduls bzw. eines Objektes in den Objektbaum, kann dieses getestet werden. Da die Veränderung des Objektbaumes noch nicht über das Programm selbst geht, muß hierzu die zu ladende XML-Datei manipuliert werden. Dazu fügt man einfach in einen bestehenden Node-Eintrag den folgenden Data-Eintrag ein:7 <data name="Examples.tracker"> <Tracker/> </data> Damit ist das Tracker-Objekt an diese Node gekoppelt und zeichnet deren Bewegungen auf. Die Einstellungen der Parameter führt man am besten nach dem Laden der Datei im Dialog zur Nodes-Konfiguration durch. Die folgenden Bilder zeigen den Konfigurationsdialog und das Resultat im Ausgabefenster nach Aktivierung der Anzeige der Spur. Man beachte die Spuren an den Rädern des Roboters. 6 Dabei werden nur neue oder geänderte Dateien verarbeitet. Anhang C auf Seite 87 findet sich eine Beispieldatei, in der durch zwei solche Einträge zwei Tracker-Objekte an die Räder des Khepera gekoppelt wurden. 7 Im 73 12. Erstellung eines Erweiterungsmoduls Abbildung 12.1.: Konfigurationsdialog des Tracker-Moduls Abbildung 12.2.: Ansicht bei angezeigter Spur des Tracker-Moduls 74 Teil IV. Sonstiges 75 13. Nutzung und Verwertung Folgendes bezieht sich sowohl auf dieses Dokument als auch auf die dazu gehörige Software. Die Nutzung und Verwertung an der HTW fällt in die nachfolgend als Ausbildungszweck bezeichnete Kategorie. Nutzung und Verwertung: Die Nutzung und Verwertung ist für private oder Ausbildungszwecke frei. Kommerzielle Nutzung irgendwelcher Art bedarf meiner vorherigen Zustimmung. Es wird keinerlei Garantie für Richtigkeit und Vollständigkeit übernommen. Weiterhin wird keinerlei Verantwortung für irgendwelche Auswirkungen der Benutzung der Software übernommen, die Risiken hat der Nutzer zu tragen. Verbreitung: Die Verbreitung ist explizit erwünscht und demzufolge frei, einzige Bedingung ist das keine anderen Kosten als die Kopierkosten dafür erhoben werden. Wichtiges: Neben der eigentlichen Software des Simulationsprogrammes werden einige Softwarepakete mitgeliefert, die zur Benutzung notwendig sind, diese haben ihre eigenen Lizenz- und Nutzungsbestimmungen. Diese kann man den Softwarepaketen entnehmen. Ausserdem sollte man in Zweifelsfall immer die aktuellsten Versionen aus dem Internet herunterladen und benutzen. Die Internetadressen findet man in den Paketen bzw. im Literaturverzeichnis. 77 14. Selbstständigkeitserklärung Ich versichere, dass ich die Diplomarbeit selbstständig verfasst und keine anderen als die angegebenen Quellen und Hilfsmittel benutzt habe. René Liebscher 78 15. Thesen • Man kann Algorithmen und Techniken zur Steuerung von Robotern auf zwei verschiedenen Wegen erforschen, entweder durch reale in Hardware realisierte Roboter oder durch Simulation der Roboter und ihrer Umwelt mittels Software. • Der Hardwareansatz ist zum Teil sehr teuer, vor allem wenn man neue Arten von Robotern erforschen möchte. Außerdem stellen die mechanischen Eigenschaften eine Grenze bezüglich der Geschwindigkeit der Bewegungen und damit der Versuche dar. • Durch Simulation der Roboter lassen sich diese Nachteile vermeiden. 1. Man kann Roboter simulieren lassen, die noch nicht existieren und so vorab testen, ob das Design dem Verwendungszweck überhaupt gerecht werden kann. (Keine Kosten für Hardware!) 2. Die Parameter des simulierten Roboters und seiner Sensoren können beliebig verändert werden, so lassen sich zum Beispiel Untersuchungen durchführen wie verschiedene Steueralgorithmen auf defekte Sensoren reagieren. 3. Mit Hilfe mehrerer Rechner lassen sich mehrere Roboter gleichzeitig simulieren, da Software, im Gegensatz zu Hardware, unbegrenzt vervielfältigbar ist. 4. Eine Simulation ist nicht an die reale Zeit gebunden, so das Simulationen schneller sein können als die reale Umsetzung. 5. Durch Software lassen sich ganze Versuchsreihen automatisieren, angefangen von der Einstellung der Parameter für den Versuch, über die Erfassung der Ergebnisse bis zur automatischen Auswertung der Resultate. • Es gibt bereits einige Simulatoren für den Miniaturroboter Khepera, aber dort sind zum Teil einige der Vorteile eines Simulators durch andere Einschränkungen nicht nutzbar. • Als prinzipielles Problem muß man hier die Nichtverfügbarkeit der Quelltexte nennen, ohne die Quelltexte sind aber Erweiterungen der Systeme unmöglich. • Hier soll das Konzept eines Simulators vorgestellt werden, der viele der genannten Vorteile eines idealen Simulators in sich vereint. • Dazu sollen folgende Eigenschaften realisiert werden. 1. Freigabe der Quelltexte und ausschließliche Nutzung von freier Software für nicht selbst erstellte Systemteile. Dadurch wird gewährleistet, daß der Simulator ohne irgendwelchen Folgekosten für externe Software durch den Anwender verändert werden kann. 79 15. Thesen 2. Modularer Aufbau, so daß jederzeit neue Module hinzugefügt oder alte durch verbesserte Versionen ersetzt werden können. Diese Module werden nahtlos in das System integriert. Das erfordert unter anderem, daß das interne Datenmodell entsprechend vorbereitet ist, als auch die Einbindung der neuen Module in die vorhandene Benutzeroberfläche. 3. Integration einer Scriptsprache, die Zugriff auf alle wesentlichen Teile des Systems erlaubt. Dadurch kann der Simulator vollständig über vom Benutzer geschriebene Scripte gesteuert werden. 4. Der Simulator soll fähig sein existierende Khepera-Steuerungsprogramme auf Quelltextebene zu übernehmen und nach Übersetzung für den Simulator, diese direkt auszuführen. 80 Teil V. Anhang 81 A. Installation des Systems A.1. Notwendige externe Software Bevor das eigentliche System installiert werden kann, müssen erst einmal alle anderen notwendigen Softwarepakete installiert sein. (Alle notwendigen Pakete befinden sich auf der CD, siehe auch Anhang B, Seite 86.) A.1.1. Python Windows Datei py152.exe aus dem Verzeichnis Externes/python/win32 starten. UNIX Datei py152.tgz aus dem Verzeichnis Externes/python/sources in ein temporäres Verzeichnis auspacken1 . Und Ausführen der folgenden Befehle2 : ./configure make install A.1.2. Distutils Windows Datei Distutils-0.9.1.win32.exe distutils/win32 starten. aus dem Verzeichnis Externes/ UNIX Datei Distutils-0.9.1.tar.gz aus dem Verzeichnis Externes/ distutils/sources in ein temporäres Verzeichnis auspacken. Und Ausführen der folgenden Befehle3 : python setup.py build python setup.py install A.1.3. PyOpenGL Windows Datei PyOpenGL.exe aus dem Verzeichnis Externes/PyOpenGL/win32 starten. UNIX Datei PyOpenGL-1.5.5-distutils.tar.gz aus dem Verzeichnis Externes/ distutils/sources in ein temporäres Verzeichnis auspacken. Und Ausführen der folgenden Befehle: 1 Wechseln in Zielverzeichnis und Ausführen der folgenden Befehle: gunzip -c py152.tgz | tar xvf ./configure -help zeigt alle verfügbaren Konfigurationsoptionen, wie zum Beispiel das Installationsverzeichnis. 3 python setup.py -help zeigt alle verfügbaren Konfigurationsoptionen. 2 83 A. Installation des Systems python setup.py build python setup.py install A.1.4. pygtk Windows Datei pygtk-0.6.6.win32.exe aus dem Verzeichnis Externes/pygtk/win32 starten. Außerdem müssen noch die zugehörigen GTK-DLL’s (aus dem Verzeichnis Externes/gtk/win32_dlls) in ein Verzeichnis, welches im Suchpfad liegt, installiert werden. Am besten man kopiert sie in das Verzeichnis in das man auch Python installiert hat (meistens C:\Programme\Python.) UNIX Datei pygtk-0.6.6.tar.gz aus dem Verzeichnis Externes/pygtk/sources in ein temporäres Verzeichnis auspacken. Und Ausführen der folgenden Befehle: ./configure make install Wichtig: Die oben genannte Vorgehensweise geht für UNIX davon aus, das OpenGL, GTK und gtkglarea bereits vorhanden sind. Falls das nicht der Fall ist, findet man diese Pakete auf der CD. (Mesa ist eine OpenGL-Implementation.) A.2. Die eigentliche Installation Nachdem alles notwendige vorhanden ist, kann jetzt das eigentliche Programm installiert werden. Da auch es mit Hilfe von Distutils installiert wird, geschieht dies ähnlich den obigen Paketen. Windows Datei RSTk-0.0.1.win32.exe aus dem Verzeichnis RSTk/win32/installer starten4 . UNIX Datei RSTk-0.0.1.tar.gz aus dem Verzeichnis RSTk/sources in ein temporäres Verzeichnis auspacken. Und ausführen der folgenden Befehle: python setup.py build python setup.py install Diese Installation mit Neukompilierung des Systems funktioniert natürlich auch unter Windows. Eine Alternative zu obigen Anweisungen besteht im Starten der Datei install-here.sh beziehungsweise install-here.bat, welche das System in das aktuelle Verzeichnis installieren. A.3. Kurztest Nun sollte ein kurzer Test zeigen, ob es alles wie erwartet funktioniert. 4 Wenn hier Fehlermeldungen auftreten, insbesondere zu Dateien mit Namen __init__.py, so ist dies normal und liegt am Entwicklungsstand des Installationsprogrammes. 84 A.4. Test-Installationen auf CD Windows Im Python-Verzeichnis findet sich ein Verzeichnis Scripts mit einer Datei rstk.bat. Wenn diese gestartet wird, sollte das Hauptfenster des Programmes erscheinen. UNIX Hier sollte der Aufruf von rstk.sh ausreichen, um das System zu starten, vorausgesetzt es wurde das Standardinstallationsziel nicht verändert. Ansonsten sollte sich die Datei rstk.sh dort finden. Nachdem das Programmes gestartet ist, lädt man nun die Datei examples/example.xml aus dem Verzeichnis in das man RSTk installiert hat. Nun aktiviert man die 3D-Ansicht und startet den Simulator, als Resultat sollte man jetzt einen im Kreis fahrenden Roboter sehen. A.4. Test-Installationen auf CD Auf der CD befinden sich zwei lauffähige Python-Installationen, wovon eine speziell für NT gedacht ist. Diese NT-Version enthält einen leicht veränderte pygtk-Version um einen Fehler im GTK (siehe auch Seite 61) zu umgehen. Allerdings läuft dadurch entweder die Simulation oder die Reaktion der Benutzeroberfläche langsamer als normal, da der Scheduler-Thread sich andauernd mit dem Hauptthread synchronisieren muß5 . Zum Ausprobieren geht man am besten wie folgt vor: Starten der Datei CD-Laufwerk:\RSTk\win32\RSTk_NT.bat oder Eintragen der Python-Installation in den PATH: set PATH=CD-Laufwerk:\RSTk\win32\test_NT;%PATH% Aufruf des Systems: CD-Laufwerk:\RSTk\win32\RSTk_NT\Scripts\rstk (Entsprechendes gilt auch für die Windows 98-Variante.) 5 Dies fällt insbesondere im Fullspeed-Betrieb auf, wo ohne Visualisierung auf meiner Maschine eine Geschwindigkeit von 30 erreicht wird, mit Visualisierung sinkt dies auf 26, wobei die weiterhin große Geschwindigkeit auf Kosten der Anzeige gehalten wird. Bei Benutzung der pygtk-NT-Version auf Windows 98 liegt die Geschwindigkeit bei 6 bis 7, bei allerdings höherer Geschwindigkeit der Anzeige, die hierbei der begrenzende Faktor und Taktgeber zugleich ist. Auf Windows NT sieht es andersherum aus, wobei die Simulation fast normal läuft, aber dafür die Oberfläche extrem träge reagiert. 85 B. Inhalt der CD RSTk/ Dateien zum Robots Simulation Toolkit sources/ win32/ Doku/ Externes/ 1 set 86 Quelltexte zum System Windows-spezifische Dinge installer/ RSTk_98/ einfacher Windows-Installer Dieses Verzeichnis enthält eine voll lauffähige PythonInstallation für Windows 98, einschließlich RSTk und allen anderen notwendigen Komponenten. Man muß nur den PATH auf dieses Verzeichnis setzen1 . RSTk_NT/ Dasselbe für Windows NT. Die GTK-Bibliothek hat Probleme mit NT, deshalb hier extra. Siehe auch Kapitel 10.3, Seite 61. Dokumentation Dies sind die externen Softwarepakete, die benutzt wurden, beziehungsweise für den Betrieb notwendig sind. Python/ distutils/ Python 1.5.2 pygtk/ PyOpenGL/ NumPy/ pygtk 0.6.6 - Python Bindings für GTK GTK/ gtkglarea/ glade/ Mesa/ cygwin/ GTK 1.2.8 - Gimp Toolkit, die grafische Benutzeroberfläche Distutils 0.9.1 - Installations- und Setupsupport für Pythonerweiterungsmodule PyOpenGL 1.5.5 - Python Bindings für OpenGL Numerical Python 15.3 - Python-Erweiterungen im Bereich Numerische Mathematik GtkGlArea 1.2.2 - Ein OpenGL-Widget für GTK glade 0.5.7 - Ein Interface-Builder für GTK Mesa 3.2.1 - Die freie OpenGL-Implementation Der GNU-C-Compiler für Windows9x/NT, einschließlich vieler Tools wie bash, make, ... PATH=Verzeichnis;%PATH% C. Beispiel XML-Daten Diese Beispieldatei entspricht der in verschiedenen Abbildungen bereits gezeigten Welt mit einem Khepera-Roboter und einem oben offenen Kasten. Dies ist somit als Minimalkonfiguration zu betrachten. Der Khepera besteht aus dem Körper (Zylinder), 2 Rädern (Zylinder) und 8 Sensoren (Quader.) Der Kasten besteht aus den 4 Seitenwänden und dem Boden (alles Quader.) <?xml version="1.0" encoding="ISO-8859-1"?> <world> <modules> <module name="Khepera.controller"/> <module name="Objects_3D.transformation"/> </modules> <node name="Universe"> <data name="View_3D.viewpoint"> <Viewpoint orientation="0 1 0 0.3927" fieldOfView="0.785398" position="250 100 250" description="Overview"/> </data> <data name="description"> Top node of world, contains: * a box-like playground * Khepera, the robot * a directional light * a viewpoint right above a corner of the playground </data> <data name="View_3D.light"> <DirectionalLight intensity="1" ambientIntensity="0" direction="1 -0.5 0" on="TRUE" color="1 1 1"/> </data> <node name="Khepera"> <data name="View_3D.light"> <SpotLight location="0 12 27.5" beamWidth="0.785398" ambientIntensity="0" attenuation="1 0 0" color="1 1 1" intensity="1" radius="100" cutOffAngle="0.785398" direction="0 0 -1" on="TRUE"/> </data> <data name="Khepera.controller"> <Program name="braiten"/> <ProximitySensor nr="0" name="0"/> <ProximitySensor nr="1" name="1"/> <ProximitySensor nr="2" name="2"/> <ProximitySensor nr="3" name="3"/> <ProximitySensor nr="4" name="4"/> <ProximitySensor nr="5" name="5"/> <ProximitySensor nr="6" name="6"/> <ProximitySensor nr="7" name="7"/> </data> <data name="Objects_3D.transformation"> <Transformation matrix="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1"/> 87 C. Beispiel XML-Daten </data> <node name="Body"> <data name="Objects_3D.transformation"> <Transformation matrix="1 0 0 0 0 1 0 0 0 0 1 0 0 12 0 1"/> </data> <data name="Objects_3D.appearance"> <Material diffuseColor="0.0235294 0.52549 0.0235294"/> </data> <data name="Objects_3D.geometry"> <Cylinder height="20" radius="27.5"/> </data> </node> <node name="Wheel, left"> <data name="Objects_3D.transformation"> <Transformation matrix="0 1 0 0 -1 0 0 0 0 0 1 0 -27.5 7.5 0 1"/> </data> <data name="Examples.tracker"> <Tracker time="0.25" color="1.0 0.0 0.0"/> </data> <data name="Objects_3D.appearance"> <Material diffuseColor="0.345098 0.560784 0.882353"/> </data> <data name="Objects_3D.geometry"> <Cylinder height="1" radius="7.5"/> </data> </node> <node name="Wheel, right"> <data name="Objects_3D.transformation"> <Transformation matrix="0 1 0 0 -1 0 0 0 0 0 1 0 27.5 7.5 0 1"/> </data> <data name="Examples.tracker"> <Tracker time="0.25" color="0.0 0.0 1.0"/> </data> <data name="Objects_3D.appearance"> <Material diffuseColor="0.345098 0.560784 0.882353"/> </data> <data name="Objects_3D.geometry"> <Cylinder height="1" radius="7.5"/> </data> </node> <node name="Sensors"> <data name="Objects_3D.appearance"> <Material diffuseColor="0 0 0"/> </data> <node name="0"> <data name="description"> Sensor, front left 80 degrees </data> <data name="Khepera.proximity_sensor"/> <data name="Objects_3D.transformation"> <Transformation matrix="0.1699671429 0 -0.98544973 0 0 1 0 0 0.98544973 0 0.1699671429 0 -25 10 -11 1"/> </data> 88 <data name="Objects_3D.geometry"> <Box size="4 3 1"/> </data> </node> <node name="1"> <data name="description"> Sensor, front left 45 degrees </data> <data name="Khepera.proximity_sensor"/> <data name="Objects_3D.transformation"> <Transformation matrix="0.70710678119 0 -0.70710678119 0 0 1 0 0.70710678119 0 0.70710678119 0 -16 10 -22 1"/> </data> <data name="Objects_3D.geometry"> <Box size="4 3 1"/> </data> </node> <node name="2"> <data name="description"> Sensor, front left 10 degrees </data> <data name="Khepera.proximity_sensor"/> <data name="Objects_3D.transformation"> <Transformation matrix="0.986685 0 -0.169182 0 0 1 0 0 0.169182 0.985585 0 -5 10 -27 1"/> </data> <data name="Objects_3D.geometry"> <Box size="4 3 1"/> </data> </node> <node name="3"> <data name="description"> Sensor, front right 10 degrees </data> <data name="Khepera.proximity_sensor"/> <data name="Objects_3D.transformation"> <Transformation matrix="0.986685 0 0.169182 0 0 1 0 0 -0.169182 0.985585 0 5 10 -27 1"/> </data> <data name="Objects_3D.geometry"> <Box size="4 3 1"/> </data> </node> <node name="4"> <data name="description"> Sensor, front right 45 degrees </data> <data name="Khepera.proximity_sensor"/> <data name="Objects_3D.transformation"> <Transformation matrix="0.70710678119 0 0.70710678119 0 0 1 0 0 0.70710678119 0 0.70710678119 0 16 10 -22 1"/> </data> <data name="Objects_3D.geometry"> 0 0 0 - 89 C. Beispiel XML-Daten <Box size="4 3 1"/> </data> </node> <node name="5"> <data name="description"> Sensor, front right 80 degrees </data> <data name="Khepera.proximity_sensor"/> <data name="Objects_3D.transformation"> <Transformation matrix="0.1699671429 0 0.98544973 0 0 1 0 0 0.98544973 0 0.1699671429 0 25 10 -11 1"/> </data> <data name="Objects_3D.geometry"> <Box size="4 3 1"/> </data> </node> <node name="6"> <data name="description"> Sensor, rear right </data> <data name="Khepera.proximity_sensor"/> <data name="Objects_3D.transformation"> <Transformation matrix="-0.98999 0 0.14112 0 0 1 0 0 -0.14112 0 -0.98999 0 5 10 27 1"/> </data> <data name="Objects_3D.geometry"> <Box size="4 3 1"/> </data> </node> <node name="7"> <data name="description"> Sensor, rear left </data> <data name="Khepera.proximity_sensor"/> <data name="Objects_3D.transformation"> <Transformation matrix="-0.98999 0 -0.14112 0 0 1 0 0 0.14112 0 -0.98999 0 -5 10 27 1"/> </data> <data name="Objects_3D.geometry"> <Box size="4 3 1"/> </data> </node> </node> </node> <node name="Playground"> <data name="Objects_3D.transformation"> <Transformation matrix="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1"/> </data> <data name="View_3D.light"> <PointLight ambientIntensity="0" color="1 1 1" location="0 100 0" radius="100" attenuation="1 0 0" intensity="1" on="TRUE"/> </data> <node name="Floor"> 90 <data name="Objects_3D.transformation"> <Transformation matrix="1 0 0 0 0 1 0 0 0 0 1 0 0 -0.5 0 1"/> </data> <data name="Objects_3D.appearance"> <Material diffuseColor="0.721569 0.607843 0.898039"/> </data> <data name="Objects_3D.geometry"> <Box size="500 1 500"/> </data> </node> <node name="Wall, left"> <data name="Objects_3D.transformation"> <Transformation matrix="1 0 0 0 0 1 0 0 0 0 1 0 -250.5 24.5 0 1"/> </data> <data name="Objects_3D.appearance"> <Material diffuseColor="0.6 0.894118 0.721569"/> </data> <data name="Objects_3D.geometry"> <Box size="1 51 502"/> </data> </node> <node name="Wall, right"> <data name="Objects_3D.transformation"> <Transformation matrix="1 0 0 0 0 1 0 0 0 0 1 0 250.5 24.5 0 1"/> </data> <data name="Objects_3D.appearance"> <Material diffuseColor="0.6 0.894118 0.721569"/> </data> <data name="Objects_3D.geometry"> <Box size="1 51 502"/> </data> </node> <node name="Wall, back"> <data name="Objects_3D.transformation"> <Transformation matrix="1 0 0 0 0 1 0 0 0 0 1 0 0 24.5 -250.5 1"/> </data> <data name="Objects_3D.appearance"> <Material diffuseColor="0.6 0.894118 0.721569"/> </data> <data name="Objects_3D.geometry"> <Box size="502 51 1"/> </data> </node> <node name="Wall, front"> <data name="Objects_3D.transformation"> <Transformation matrix="1 0 0 0 0 1 0 0 0 0 1 0 0 24.5 250.5 1"/> </data> <data name="Objects_3D.appearance"> <Material diffuseColor="0.6 0.894118 0.721569"/> </data> <data name="Objects_3D.geometry"> <Box size="502 51 1"/> </data> 91 C. Beispiel XML-Daten </node> </node> </node> </world> 92 D. Sonstige Quelltexte D.1. build_dll.py Hilfsprogramm zum Erstellen der Steuerungs-DLL’s. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 """ Build a dll/so in a platform specific manner We let distutils figure out how to compile. Most parameters are the same as distutils uses for its Extensions specification. """ import os,sys from distutils.sysconfig import customize_compiler from distutils.ccompiler import new_compiler from distutils.dep_util import newer_group def build_dll( compiler = None, # "unix","msvc","mingw32",.. # see distutils --help-compiler verbose = 1, # 0 dry_run = 0, # 1 force = 0, # 1 debug = 0, # 1 extra_compile_args = [], # ["-Wall","-O"] extra_link_args = [], build_temp = ".", sources = [], include_dirs = ["."], output_name = "noname", output_dir = ".", export_symbols = None, libraries = [], library_dirs = ["."], runtime_library_dirs = None ): # Setup the CCompiler object that we’ll use to do all the # compiling and linking compiler_obj = new_compiler (compiler=compiler, verbose=verbose, dry_run=dry_run, force=force) 93 D. Sonstige Quelltexte 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 D.2. customize_compiler(compiler_obj) # how to call the result on this platform output_filename = compiler_obj.shared_object_filename( basename=output_name, output_dir=output_dir) # check if really compiling is really needed if not (force or newer_group(sources, output_filename, ’newer’)): sys.stderr.write ("skipping ’%s’ (up-to-date)\n" % output_filename) else: sys.stderr.write ("building ’%s’" % output_filename) # Next, compile the source code to object files. extra_args = extra_compile_args or [] extra_args.append("-DSIMULATION") if os.environ.has_key(’CFLAGS’): extra_args.extend(string.split(os.environ[’CFLAGS’])) # compile all source files objects = compiler_obj.compile (sources, output_dir=build_temp, #macros=macros, include_dirs=include_dirs, debug=debug, extra_postargs=extra_args) extra_args = extra_link_args or [] # link all object files to the dll compiler_obj.link_shared_object ( objects, output_filename, libraries=libraries, library_dirs=library_dirs, runtime_library_dirs=runtime_library_dirs, extra_postargs=extra_args, export_symbols=export_symbols, debug=debug, build_temp=build_temp) Quelltexte zum Trackermodul Diese Quelltexte sind nicht mit den endgültigen identisch. Sie sind hier abgedruckt um einen Eindruck von dem ungefähren Umfang eines kleineren Erweiterungsmoduls zu geben. Für genaueres Verständnis sollte man vorher unbedingt die Python-Dokumentation [38] insbesondere den Teil über die CErweiterungen gelesen haben. 94 D.2. Quelltexte zum Trackermodul D.2.1. tracker.h Der C-Teil des Moduls, Header-Datei. 1 2 3 4 5 6 7 8 9 10 11 12 /* -*- Mode: C; c-basic-offset: 2 -*- */ /* tracker * Copyright (C) 2000 Rene Liebscher <[email protected]> */ #ifndef _RSTK_TRACKER_H_ #define _RSTK_TRACKER_H_ #include <RSTk/config.h> #endif /* !_RSTK_TRACKER_H_ */ D.2.2. _tracker.c Der C-Teil des Moduls, Implementierung. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 /* -*- Mode: C; c-basic-offset: 2 -*- */ /* tracker * Copyright (C) 2000 Rene Liebscher <[email protected]> */ #define _INSIDE_TRACKER_ #include <RSTk/modules/Examples/tracker.h> #include #include #include #include #include #include <RSTk/utils/unique_string.h> <RSTk/utils/callback.h> <RSTk/utils/nodes.h> <RSTk/utils/scheduler.h> <RSTk/modules/Objects_3D/transformation.h> <RSTk/modules/View_3D/opengl.h> #define VERSION "0.0.1" #include <stdio.h> #include <GL/gl.h> static static static static static static static UniqueString UniqueString UniqueString UniqueString UniqueString UniqueString UniqueString US_data_add; US_data_delete; US_data_changed; US_View_3D_opengl; US_Examples_tracker; US_draw_userobjects; US_update_world2; typedef struct _pos_time { vector4d pos; double time; 95 D. Sonstige Quelltexte 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 96 struct _pos_time *next; struct _pos_time *prev; } pos_time; struct _TrackerObject { PyObject_HEAD PyObject * node; BOOLEAN record; BOOLEAN record_all; double time; BOOLEAN show; vector4d color; struct _pos_time ring_list; int list_count; long update_world2_id; long draw_userobjects_id; }; /* /* /* /* /* /* /* /* /* /* /* Python internals */ which Objekt is tracked */ recording ? */ unlimited recording */ time limit */ show track */ with which color */ list of samples */ number of entries */ id of scheduler callback function */ id of opengl callback function */ /* Callback handler */ static void _draw_tracker(CallbackObject * PyObject * data, static void _update_tracker(CallbackObject PyObject * data, cb, UniqueString slotname, PyObject * handler_data); * cb, UniqueString slotname, PyObject * handler_data); static PyTypeObject TrackerObject_Type; #define Tracker_New() new_object() #define Tracker_Check(op) (((PyObject*)op)->ob_type == &TrackerObject_Type) /* module functions _wrap_????? */ static PyObject *_wrap_new(PyObject * self, PyObject * args); /* object methods _tracker_???? */ static PyObject *_tracker_opengl_execute(PyObject * self, PyObject * args); /* internal object functions */ static void __tracker_dealloc(TrackerObject * cb_obj); static PyObject *__tracker_getattr(TrackerObject * self, char *name); static int __tracker_setattr(TrackerObject * self, char *name, PyObject * data); static PyObject *__tracker_repr(TrackerObject * self); /**************************************************************** **** Implementation ***************************************************************/ /* insert new sample in list */ static INLINE void insert_first(TrackerObject * tracker, double time, vector4d_ref pos) { pos_time *x1 = &(tracker->ring_list); pos_time *x2 = tracker->ring_list.next; pos_time *pt = PyMem_Malloc(sizeof(pos_time)); pt->time = time; D.2. 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 Quelltexte zum Trackermodul copy_vector_3d1(pos, pt->pos); pt->next = x2; pt->prev = x1; x2->prev = pt; x1->next = pt; tracker->list_count++; } /* remove last sample from list */ static INLINE void remove_last(TrackerObject * tracker) { pos_time *x = tracker->ring_list.prev; x->prev->next = x->next; x->next->prev = x->prev; tracker->list_count--; PyMem_Free(x); } /* draw data using OpenGL */ static INLINE void opengl_execute(TrackerObject * tracker) { assert(tracker != NULL); assert(Tracker_Check(tracker)); if (tracker->show && (tracker->list_count >= 2)) { glPushAttrib(GL_ALL_ATTRIB_BITS); glDisable(GL_LIGHTING); glColor3dv(tracker->color); glBegin(GL_LINE_STRIP); { /* scan list */ pos_time *pt = tracker->ring_list.next; while (pt != &(tracker->ring_list)) { glVertex3dv(pt->pos); pt = pt->next; }; }; glEnd(); glPopAttrib(); } } /* callback handler for OpenGL.draw_userobjects */ static void _draw_tracker(CallbackObject * cb, UniqueString slotname, PyObject * data, PyObject * handler_data) { TrackerObject *tracker; assert(slotname == US_draw_userobjects); assert(Tracker_Check(handler_data)); tracker = (TrackerObject *) handler_data; opengl_execute(tracker); } /* callback handler for Scheduler.update_world2 */ 97 D. Sonstige Quelltexte 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 98 static void _update_tracker(CallbackObject * cb, UniqueString slotname, PyObject * data, PyObject * handler_data) { TrackerObject *tracker; assert(slotname == US_update_world2); assert(Tracker_Check(handler_data)); tracker = (TrackerObject *) handler_data; if (tracker->node && tracker->record) { double time = Scheduler_GetSimTime(); matrix4d matrix; vector4d pos; /* get transformation matrix to world coordinates */ Transformation_GetNodeTransformation(tracker->node, matrix, 1); copy_vector_3d1(&(matrix[12]), pos); /* remove last if timed out */ while (!(tracker->record_all) && (tracker->list_count >= 2) && (time - tracker->ring_list.prev->time > tracker->time)) { remove_last(tracker); } /* save memory, if there are three steps at the same place, we can also store start and end of this non-movement */ if ((tracker->list_count >= 2) && compare_vectors(pos, tracker->ring_list.next->pos) && compare_vectors(pos, tracker->ring_list.next->next->pos)) { tracker->ring_list.next->time = time; } else { insert_first(tracker, time, pos); } } } /**************************************************************** **** Python wrapper part of module ***************************************************************/ /* definition of the Tracker Object */ /* type definition of our Tracker object */ static PyTypeObject TrackerObject_Type = { PyObject_HEAD_INIT(NULL) 0, /*ob_size */ "Tracker", /*tp_name */ sizeof(TrackerObject), /*tp_size */ 0, /*tp_itemsize */ /* methods */ (destructor) __tracker_dealloc, /*tp_dealloc */ 0, /*tp_print */ (getattrfunc) __tracker_getattr, /*tp_getattr */ (setattrfunc) __tracker_setattr, /*tp_setattr */ D.2. 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 0, (reprfunc) __tracker_repr, 0, 0, 0, 0 Quelltexte zum Trackermodul /*tp_compare */ /*tp_repr */ /*tp_as_number */ /*tp_as_sequence */ /*tp_as_mapping */ /*tp_hash */ }; /* methods of the Tracker object */ static struct PyMethodDef TrackerMethods[] = { {"opengl_execute", (PyCFunction) _tracker_opengl_execute, METH_VARARGS}, {NULL, NULL} }; /* object no longer needed by Python */ static void __tracker_dealloc(TrackerObject * tracker) { /* destructor for TrackerObject */ if (tracker == NULL) return; /* unregister callback handlers */ Callback_DeleteHandler(Scheduler_Callback, US_update_world2, tracker->update_world2_id); Callback_DeleteHandler(OpenGL_Callback, US_draw_userobjects, tracker->draw_userobjects_id); /* remove samples list */ while (tracker->list_count > 0) remove_last(tracker); PyMem_DEL(tracker); } /* manage object attributes */ static PyObject *__tracker_getattr(TrackerObject * self, char *name) { PyObject *res = NULL; PyErr_Clear(); /* read objects attributes */ if (strcmp("node", name) == 0) { res = self->node; if (res == NULL) res = Py_None; Py_XINCREF(res); return res; } else if (strcmp("record", name) == 0) { return PyInt_FromLong(self->record); } else if (strcmp("record_all", name) == 0) { return PyInt_FromLong(self->record_all); } else if (strcmp("show", name) == 0) { 99 D. Sonstige Quelltexte 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 100 return PyInt_FromLong(self->show); } else if (strcmp("time", name) == 0) { return PyFloat_FromDouble(self->time); } else if (strcmp("color", name) == 0) { return Py_BuildValue("(ddd)", self->color[0], self->color[1], self->color[2]); } /* object methods are function objects from the methods table */ return Py_FindMethod(TrackerMethods, (PyObject *) self, name); } /* manage object attributes */ static int __tracker_setattr(TrackerObject * self, char *name, PyObject * data) { /* return 0 means OK, -1 error */ /* write object attributes */ if (strcmp("record", name) == 0) { self->record = PyObject_IsTrue(data); } else if (strcmp("record_all", name) == 0) { self->record_all = PyObject_IsTrue(data); } else if (strcmp("show", name) == 0) { self->show = PyObject_IsTrue(data); } else if (strcmp("time", name) == 0) { self->time = PyFloat_AsDouble(data); } else if (strcmp("color", name) == 0) { if (!PyArg_ParseTuple(data, "ddd|d", &self->color[0], &self->color[1], &self->color[2], &self->color[3])) return -1; } else { PyErr_SetString(PyExc_AttributeError, name); return -1; } return 0; } /* string representation */ static PyObject *__tracker_repr(TrackerObject * self) { char buf[50]; sprintf(buf, "<Tracker Object at %p>", self); return PyString_FromString(buf); } /* end of object specific functions */ /* module functions _wrap_????? */ /* create new object from Python */ /* create a new Tracker object */ static PyObject *_wrap_new(PyObject * self, PyObject * args) { D.2. 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 Quelltexte zum Trackermodul TrackerObject *tracker; if (!PyArg_ParseTuple(args, ":Tracker")) return NULL; tracker = PyObject_NEW(TrackerObject, &TrackerObject_Type); tracker->record = BOOLEAN_TRUE; tracker->record_all = BOOLEAN_FALSE; tracker->time = 1.0; tracker->show = BOOLEAN_TRUE; tracker->color[0] = 1.0; tracker->color[1] = 1.0; tracker->color[2] = 1.0; tracker->color[3] = 1.0; tracker->ring_list.next = &tracker->ring_list; tracker->ring_list.prev = &tracker->ring_list; tracker->list_count = 0; /* register the callback handlers */ tracker->draw_userobjects_id = Callback_AddHandlerAfter(OpenGL_Callback, US_draw_userobjects, _draw_tracker, (PyObject *) tracker); tracker->update_world2_id = Callback_AddHandlerAfter(Scheduler_Callback, US_update_world2, _update_tracker, (PyObject *) tracker); return (PyObject *)tracker; } /* object method */ static PyObject *_tracker_opengl_execute(PyObject * self, PyObject * args) { /* check for arguments */ if (!PyArg_ParseTuple(args, ":opengl_execute")) return NULL; opengl_execute((TrackerObject *) self); Py_INCREF(Py_None); return Py_None; } /* table of python methods for this module */ static PyMethodDef trackerMethods[] = { /* only this function to create a new Tracker object */ {"Tracker", _wrap_new, METH_VARARGS, "creates a tracker object"}, {NULL, NULL} }; /* handler for Nodes.data_add */ static void _node_data_add(CallbackObject * cb, UniqueString slotname, PyObject * data, PyObject * handler_data) { 101 D. Sonstige Quelltexte 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 102 TrackerObject *tracker; assert(slotname == US_data_add); assert(PyTuple_Check(data)); if (PyTuple_GetItem(data, 1) != US_Examples_tracker) return; /* tracker object assigned to node */ tracker = (TrackerObject *) PyTuple_GetItem(data, 2); assert(Tracker_Check(tracker)); /* no incref because we want to avoid circular references */ tracker->node = PyTuple_GetItem(data, 0); } /* handler for Nodes.data_delete */ static void _node_data_delete(CallbackObject * cb, UniqueString slotname, PyObject * data, PyObject * handler_data) { TrackerObject *tracker; assert(slotname == US_data_delete); assert(PyTuple_Check(data)); if (PyTuple_GetItem(data, 1) != US_Examples_tracker) return; /* tracker object removed from node */ tracker = (TrackerObject *) PyTuple_GetItem(data, 2); assert(Tracker_Check(tracker)); tracker->node = NULL; } /* handler for Nodes.data_changed */ static void _node_data_changed(CallbackObject * cb, UniqueString slotname, PyObject * data, PyObject * handler_data) { TrackerObject *tracker_new, *tracker_old; assert(slotname == US_data_changed); assert(PyTuple_Check(data)); if (PyTuple_GetItem(data, 1) != US_Examples_tracker) return; /* tracker object changed at node */ tracker_new = (TrackerObject *) PyTuple_GetItem(data, 2); tracker_old = (TrackerObject *) PyTuple_GetItem(data, 3); assert(Tracker_Check(tracker_new)); assert(Tracker_Check(tracker_old)); /* no dec/incref because we want to avoid circular references */ tracker_old->node = NULL; tracker_new->node = PyTuple_GetItem(data, 0); } /**************************************************************** **** module initialization ***************************************************************/ static char *__doc__ = "Example module"; /* initialization function of this module (shared library) */ D.2. 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 Quelltexte zum Trackermodul void init_tracker() { PyObject *m, *d; /* this is a bug workaround for MSVC */ TrackerObject_Type.ob_type = &PyType_Type; TrackerObject_Type.tp_doc = "Tracker"; /* doc-string for type */ /* mandatory for all our modules */ RSTk_Init(); init_unique_string(); init_callback(); init_nodes(); init_scheduler(); init_transformation(); init_opengl(); US_data_add = UniqueString_FromString("data_add"); US_data_delete = UniqueString_FromString("data_delete"); US_data_changed = UniqueString_FromString("data_changed"); US_View_3D_opengl = UniqueString_FromString("View_3D.opengl"); US_Examples_tracker = UniqueString_FromString("Examples.tracker"); US_draw_userobjects = UniqueString_FromString("draw_userobjects"); US_update_world2 = UniqueString_FromString("update_world2"); Callback_AddHandlerAfter(Node_Callback, US_data_add, _node_data_add, NULL); Callback_AddHandlerAfter(Node_Callback, US_data_delete, _node_data_delete, NULL); Callback_AddHandlerAfter(Node_Callback, US_data_changed, _node_data_changed, NULL); m = Py_InitModule3("RSTk.modules.Examples._tracker", trackerMethods, __doc__); d = PyModule_GetDict(m); if (PyErr_Occurred()) Py_FatalError("can’t initialise module _tracker"); } D.2.3. tracker.py Der Python-Teil des Moduls. 1 2 3 4 5 6 7 from from from from RSTk.utils.repository import * RSTk.utils.xml import xml_loader_interface,xml_loader_error RSTk.utils.xml_util import * _tracker import * # module interface for repository class _tracker_module(module_template): 103 D. Sonstige Quelltexte 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 104 def get_instance_loader(self,root,node): loader = _tracker_Loader(root,node) return loader def save_instance(self,xmlsaver,data): xmlsaver.starttag(’Tracker’,{ "record": bool_str(data.record), "record_all": bool_str(data.record_all), "time" : float_str(data.time), "show": bool_str(data.show), "color": float3_str(data.color), }) xmlsaver.endtag(’Tracker’) xmlsaver.data("\n") def get_instance_config(self, node): return instance_config(node) # load xml data for module instance class _tracker_Loader(xml_loader_interface): def __init__(self, root, node): xml_loader_interface.__init__(self, root) self.node = node def starttag(self, tag, attributes): if tag == "Tracker": t = Tracker() for name in attributes.keys(): if name == "record": t.record = str_bool(attributes[name]) elif name == "record_all": t.record_all = str_bool(attributes[name]) elif name == "show": t.show = str_bool(attributes[name]) elif name == "time": t.time = str_float(attributes[name]) elif name == "color": t.color = str_float3(attributes[name]) else: xml_loader_error(msg=("Unknown attribut %s"% name), tag=tag,attributes=attributes) self.node[get_module_name(__name__)] = t else: xml_loader_error(msg="Unknown tag", tag=tag, attributes=attributes) # instance config object class instance_config(instance_config_template): def __init__(self,node): self.__node = node D.2. 61 62 63 64 65 66 67 68 69 70 Quelltexte zum Trackermodul data = node[get_module_name(__name__)] import tracker_gui.instance_config mainObj = tracker_gui.instance_config.get_ConfigWidget(data) instance_config_template.__init__(self,mainObj.Config, mainObj.AccelGroup) self.__mainObj = mainObj # register in repository register_module(get_module_name(__name__), _tracker_module(get_module_name(__name__))) 105 Glossar Die nachfolgenden Einträge erläutern nur Begriffe zu denen kein Eintrag im Quellenverzeichnis existiert. Wenn solch ein Eintrag existiert, findet man dort eine kurze Erläuterung und einen Verweis auf weiterführende Informationen. B Bounding Box Dies ist ein Quader, der genau so groß ist, das keines der untergeordneten Objekte aus ihm herausragt. Dadurch kann man frühzeitig feststellen, ob die enthaltenen Objekte weiter untersucht werden müssen oder ob diese außerhalb des interssanten Gebietes liegen. Weitere Links zu diesem Thema finden sich auf der SOLID-Webseite[3]. C Callback Eine Callback-Funktion ist eine Funktion im eigenen Programm, welche zur Abarbeitung von Events oder Signalen zuständig ist. Diese Funktion muß der entsprechenden Einrichtung bekannt gemacht werden und wird dann von dort ohne weiteres Zutun bei Auftreten des Events oder Signals aufgerufen. D Deadlock Ein Deadlock tritt auf, wenn ein Thread auf eine Ressource wartet, die er selbst oder ein anderer Thread schon belegt hat, wobei im zweiten Fall dieser andere Thread auf eine Ressource wartet, die vom ersten Thread belegt ist. Dies führt dann dazu, daß der Thread „ewig“ wartet. Displayliste Eine Displayliste ist eine OpenGL-interne Datenstruktur in die OpenGL-Anweisungen gespeichert werden können. Mittels einer weiteren OpenGL-Anweisung kann die komplette Liste ausgeführt werden als wären die ursprünglichen Anweisungen stattdessen gegeben wurden. Display-Listen arbeiten besonders effektiv, wenn die gespeicherten Anweisungen häufig gebraucht werden, da so zum Beispiel unter X die Geschwindigkeit gesteigert wird, wenn die Liste im X-Server gespeichert ist und nur der Aufrufbefehl übertragen werden muß. Document Type Definition (DTD) Diese beschreibt den strukturellen Aufbau von XML-Daten. Damit werden die Namen der Tags und Attribute und und deren erlaubte Inhalte festgelegt. Dynamic Link Library (DLL) eine Sammlung fertig übersetzer Funktionen, die zur Laufzeit in ein Programm geladen werden kann. Unter UNIX auch shared library genannt. 106 GLOSSAR E Event Dies ist ein, durch eine externe Instanz ausgelöstes, Ereignis, welches dazu führt, daß, dem Ereignis zugeordnete, Callback-Funktionen ausgeführt werden. Events treten vollkommen asynchron auf. Beispiel sind Aktionen des Nutzers wie das Verschieben des Anwendungsfensters, die dann vom Fenstermanager dem Programm mitgeteilt werden. eXtended Markup Language (XML) Eine Sprache, die den Aufbau strukturierter Daten beschreibt, in dem die Struktureinheiten durch Tags eingeschlossen (markiert) werden. G GTK-Eventbox Dies ist ein untergeordnetes Fenster, das dazu benutzt wird um andere GTKElemente(, die kein eigenes Fenster haben) in ein Fenster einzuschliessen. In GTK kann man Events (Mausbewegungen, ...) nur von Fenstern bekommen. N Node Dies ist ein einzelnes Element im Objektbaum. Eine Node kann weitere untergeordnete Nodes haben. Eine Node kann beliebige Objektattribute aufnehmen, einschließlich einer Geometrie und eines Aussehens, womit sie in der Visualisierung sichtbar wird. O Off-Screen-Rendering Beim Off-Screen-Rendering werden die berechneten OpenGL-Daten anstatt in ein Bildschirmfenster in einen internen Speicherbereich gezeichnet. Dieser Speicherbereich enthält dann praktisch dasselbe Bild, das auf dem Bildschirm zu sehen wäre. Manche OpenGL-Implementation arbeiten generell so, und zeichnen am Ende das fertige Bild auf den Bildschirm. OpenGL Dies ist eine Sprachdefinition, die die Arbeit mit 3D-Ausgabegeräten standardisiert. OpenGL-Kontext OpenGL arbeitet intern mit einer Art Zustandsautomat. Es kann mehrere davon geben, wobei immer nur einer aktuell ist. Der Kontext bezeichnet hierbei einen solchen Zustandsautomaten. OpenGL-Anweisungen werden immer durch den aktuellen Kontext/Zustandsautomaten bearbeitet. P Parser Dies ist ein Programm(teil), welches übergebene Daten in Datenpakete zerlegt, die die Strukturelemente der Sprachdefinition wiederspiegeln. 107 GLOSSAR S Scheduler Ein Scheduler dient dazu die Reihenfolge von anstehenden Aufgaben(hier der Abarbeitung der Simulationsthreads) zur Bearbeitung abhängig von bestimmten Prioritäten und anderen Einflußfaktoren festzulegen. Im Simulationssystem wird immer der Thread zuerst ausgeführt, der am weitesten mit seiner Simulationszeit zurückliegt. Shared Library siehe DLL. Shared Object siehe DLL. Signal Signale (im Sinne von GTK und diesem Programm) werden vom Programm selbst erzeugt um irgendwelche Veränderungen anzuzeigen. Auf das Signal kann von verschiedenen Programmteilen reagiert werden, diese haben dazu nur eine Callback-Funktion zu registrieren. Signale werden synchron vom eigenen Programm ausgelöst. Special Interest Group (SIG) Eine Gruppe von Leute, die sich für eine bestimmte Sache interessieren und deren Realisierung vorantreiben. Zum Beispiel arbeitet die Distutils-SIG an der Entwicklung von Distutils[16]. T Tag Ein Tag ist ein, mit einem bestimmten Namen markiertes, Element in XML, welches zur Definition eines bestimmten Bereichs diesen umschließt. Thread Ein Thread ist ein eigenständiger Programmabarbeitungspfad mit eigenen Registern und Befehlszähler. Ein Programm kann aus mehreren Threads bestehen. Thread-safe Eine Funktion oder Programmbibliothek ist thread-safe programmiert, wenn mehrere Threads damit arbeiten können ohne das sie sich gegenseitig oder die Arbeit der Funktion negativ beeinflussen. Diese Beeinflussung findet meist über interne Datenbereiche statt, auf die immer nur ein Thread gleichzeitig Zugriff haben sollte. Toplevel-Fenster Dies ist ein Fenster, welches mittels dem Fenstermanager verwaltet werden kann. Es hat meistens einen Rahmen an dem man die Größe und Position des Fensters ändern kann. Schaltflächen oder Eingabefelder in solchen Toplevel-Fenstern sind dann untergeordnete Fenster. 108 Quellenverzeichnis Da heutzutage alle notwendigen Informationen auch im Internet zu finden sind und man so kaum auf gedruckte Bücher angewiesen ist, macht ein „Literatur“-Verzeichnis der klassischen Art hier nicht viel Sinn. Aus diesem Grund werden zu allen benutzten Quellen und Softwarepaketen auch die jeweiligen Internetadressen angegeben. Die meisten Einträge sind mit einer kurzen Beschreibung versehen, so das auf entsprechende Glossary-Einträge verzichtet wurde. [1] Edward Angel. Interactive computer graphics: a top-down approach with OpenGL. AddisonWesley, Reading, MA, 1997. [2] Bart Barenburg. DynaMo - Dynamic Motion library. http://www.win.tue.nl/cs/tt/ bartb/dynamo/. Ein System zur Simulation der dynamischen Effekte bewegter Objekte. Zu jedem Objekt müssen Masse, angreifende Kräfte, Drehmomente und die zugehörigen Trägheitsvektoren gegeben werden. Braucht zur Unterstützung noch eine Software die Kollisionen erkennt (z.B. SOLID), Dynamo selbst betrachtet alle Objekte als Massepunkte. [3] Gino van den Bergen. SOLID - Software Library for Inference Detection. http://www.win.tue. nl/cs/tt/gino/solid/. Ein Kollisionserkennungssystem. Grundkörper sind alle in VRML vorkommenden Objektarten. - Außerdem guter Startpunkt für weitere Recherchen über Kollisionserkennung, Vergleiche verschiedener Strategien dafür und so weiter. [4] James Henstridge. PyGTK - Python Bindings for the GTK Widget Set. http://www.daa.com. au/~james/pygtk/. Python Interface für GTK[22] und seit Version 0.6.4 auch für GtkGlArea[9]. Es gibt zwei Interfacevarianten: eine 1:1 Abbildung der GTK-C-Schnittstelle, und eine objektorientierte, bei der die GTK-Objekte durch Python-Objekte gekapselt werden. Außerdem besteht die Möglichkeit Glade-Dateien direkt einzulesen. Die neuesten Versionen sind Teil der Gnome-Python-Anbindung http://www.gnome.org/ applist/view.php3?name=gnome-python. [5] K-Team. Khepera User Manual. Lausanne, 1999. Beschreibung des Aufbaus des Khepera, seiner Sensoren und der Steuerung über das serielle Interface. [6] René Liebscher. Beleg im Fach Java 3D-Programmierung. http://www.htw-dresden.de/ ~htw7192/studium/java3d/. Simulation eines Billardspiels. Der Zeittakt wird durch Java 3D festgelegt, das Echtzeitverhalten wird durch Orientierung an der realen Zeit erreicht. Auf langsamen Rechnern werden weniger Bilder pro Sekunde angezeigt, die Simulation selbst bleibt aber weitestgehend unbeeinflusst, da die längeren Zeittakte berücksichtigt werden. [7] René Liebscher. Beleg in den Fächern Fuzzy-Systeme/Neuronale Netze. http://www. htw-dresden.de/~htw7192/studium/neuro1/Beleg/. Simulation eines inversen Pendels und dessen Steuerung. Hier als Beispiel einer Simulation mit festen Zeittakt für die Steuerung und 109 Quellenverzeichnis asynchronem Betrieb der Visualisierung in einem anderen Thread. Bei Aktivierung aller Anzeigeoptionen wird die Anzeigefrequenz geringer, während die Simulation im Hintergrund in Echtzeit weiterläuft. [8] Dumpleton Software Consulting Pty Limited. Python megawidgets. http://www.dscpl.com. au/pmw/. Erweiterung des TkInter-Interfaces in Python um in Tk nicht verfügbare Elemente, wie z.B. Trees, Notebooks usw. [9] Janne Löf. GtkGlArea. http://www.student.oulu.fi/~jlof/gtkglarea/. GTK-Widget, welches ein OpenGL-Rendering-Context kapselt. [10] Axel Löffler, Francesco Mondada, and Ulrich Rückert, editors. Experiments with the Mini-Robot Khepera - Proceedings of the 1st International Khepera Workshop. HNI-Verlagsschriftenreihe, Band 64. Heinz Nixdorf Institut, Paderborn, 1999. [11] . . . . BeOS. http://www.beos.com. Ein alternatives kommerzielles Betriebssystem. Gedacht für Multimedia-Anwendungen. Es gibt auch eine „freie“ Variante: BeOS 5 Personal Edition. [12] . . . . Contest: ALife Creators II. http://www.cyberbotics.com/contest/. Ein Wettbewerb in dem verschiedene simulierte Roboter, in einer Umgebung mit begrenzten Ressourcen, auf ihre Überlebensfähigkeit getestet werden. Immer 2 Roboter gegeneinander (eine Art Turniersystem), daher das ideale Beispiel für eine Anwendungsmöglichkeit scriptgesteuerter Simulation. [13] . . . . CORBA. http://www.omg.org/corba. Dies ist die Spezifikation eines Standards zur Definition von objektorientierten Interfaces zur Kommunikation zwischen mehreren Programmen, auch über Rechnergrenzen hinweg. [14] . . . . Crystal Space - A Free 3D Engine. http://crystal.linuxgames.com. Dies ist ein 3D-System, welches eigentlich für Spiele entwickelt wurde, aber aufgrund einiger Eigenschaften wie zum Beispiel einer eingebauten Dynamiksimulation und Scripting-Support (Python) könnte es durchaus auch als Grundlage für einen Robotsimulator dienen. Allerdings ist es auch sehr umfangreich, insbesondere benötigt es viele externe Bibliotheken, und die muß man erst einmal zusammentragen muß, um es kompilieren zu können. [15] . . . . Cygnus GCC for Win32. http://sources.redhat.com/cygwin/. Die Windows-Version des GNU Compiler Collection. [16] . . . . Distutils. http://www.python.org/sigs/distutils-sig/. Ein Installations- und Setup-Support-Erweiterungspacket für Python. [17] . . . . Easybot. http://www.htw-dresden.de/~iwe/easybot/easybot.html. Dies sind die Resultate des Projektseminars „Roboterprogrammierung“, mit dem bisher an der Hochschule benutzten Simulator Easybot und einigen bereits realisierten Projekten. Zum Beispiel: Steuerung durch ein Labyrinth mittels Neuronalen Netz, bzw. dasselbe Problem mittels Genetischen Algorithmus gelöst. [18] . . . . Ergebnisse des Projektseminars „Roboterprogrammierung“. Auf CD veröffentlicht. Auf dieser CD befindet sich ein Dokument, welches die Erstellung von Controllern für den Simulator Easybot und die damit realisierten Projekte beschreibt. Außerdem enthält die CD die komplette Software dazu, die Manuals zum Khepera, einige externe Software und eine Sammlung von Beschreibungen und Resultaten früherer Versuche der Khepera-Entwickler. Siehe auch [17]. 110 Quellenverzeichnis [19] . . . . GLADE GTK+ User Interface Builder. http://glade.pn.org/. Interface-Builder für GTK. Man baut seine Benutzeroberfläche ähnlich wie in Delphi (ohne Programmierung). Die fertige Oberfläche kann dann unter anderem als C, C++, Perl oder Ada-Code generiert werden. Mit Hilfe der libglade-Bibliothek kann man die Glade-Datei (XML) auch direkt im Endprogramm einlesen. [20] . . . . Glade Python Code Generator. http://glc.sourceforge.net/. Python-Programm, welche von Glade erzeugte Dateien liest und deren Inhalt als Python-Quelltext ausgibt. (Bedarf noch einiger Entwicklung.) Die, dem RSTk beigelegte, Variante basiert auf diesem Programm wurde aber erheblich verbessert. Leider bekommt man von der genannten Webseite keinerlei Reaktion auf diese Verbesserungsvorschläge. [21] . . . . gleem: OpenGL Extremely Easy-to-use Manipulators. http://www.gnu.org/software/ gleem/gleem.html. Stellt einfache 3D-Elemente zur Verfügung, ist bisher aber nur für SGI und NT verfügbar, außerdem definitiv nicht Thread-tauglich. [22] . . . . GTK+ - The GIMP Toolkit. http://www.gtk.org. Eine grafisches Toolkit, welches zum Beispiel die Grundlage für die Benutzeroberfläche Gnome ist. Komplett freier Quelltext. Ursprünglich nur für UNIX entwickelt, sind derzeit Ports für BeOS und Win32 in Arbeit bzw. schon fertig. [23] . . . . GuileGL: OpenGL language bindings for Guile. http://atrey.karlin.mff.cuni.cz/ ~0rfelyus/guileGL/. Guile-GTK-Erweiterung, welche die Nutzung von OpenGL und GtkGlArea ermöglicht. [24] . . . . (guile-gtk) Homepage. http://www.ping.de/sites/zagadka/guile-gtk/. GuileErweiterung, welche Zugriff auf GTK ermöglicht. [25] . . . . Guile: Project GNU’s extension language. http://www.gnu.org/software/guile/. Guile (Gnu Ubiquitous Intelligent Language Extension). Die von GNU preferierte Sprache um Programme scriptfähig zu machen. Es ist Scheme nach Revision 4 der Sprache. Also Lispstyle mit Unmengen an Klammern, ansonsten sehr brauchbar. [26] . . . . Java 3D Home Page. http://www.sun.com/desktop/java3d. Java 3D ist eine Erweiterung zum Java 2 JDK. Es realisiert eine Programmierschnittstelle zur Erzeugung, Visualisierung und Interaktion mit drei-dimensionalen Objekten. [27] . . . . JPython Homepage. http://www.jpython.org. Eine vollkommen in Java realisierte Variante von Python[40]. [28] . . . . K-Team. http://www.k-team.com/. Die Herstellerfirma des Kheperas und anderer Miniroboter. [29] . . . . Lesstif. http://www.lesstif.org. Lesstif ist ein Nachbau des kommerziell vertriebenen Motif. Es ist frei verfügbar. [30] . . . . LightVision 3D. http://www.lightgraphx.de. Ein 3D-Programm, welches neben der Modellierung von 3D-Welten, auch durch Plugins erweitert werden kann. Solche Plugins realisieren zum Beispiel eine Ausgabe mittels Raytracing oder auch den Robotersimulator Easybot. [31] . . . . LLNL Python Extensions. http://numpy.sourceforge.net/. Numerical Python Extensions. Ergänzt Python um Arrays von Elementen festen Datentyps(Bytes, Integers, Floats, . . . ). Außerdem sind Funktionen zum Gleichungssystem lösen, für Matrixrechnung, FFT und vieles andere enthalten. 111 Quellenverzeichnis [32] . . . . MAM/VRS. http://wwwmath.uni-muenster.de/informatik/u/mam/. 3D-Visualisierungssystem, kann OpenGL benutzen und in GTK eingebunden werden. [33] . . . . Motif. http://www.opengroup.org/motif/. Kommerziell vertriebenes Toolkit für UNIX. Wahrscheinlich das älteste hier aufgeführte Toolkit. Es ist weitverbreitet, viele kommerzielle Programme (besonders solche die schon länger auf dem Markt sind) benutzen es. Seit Mai 2000 gibt es auch eine Public Licence. [34] . . . . OpenGL 1.2 Specification. http://www.opengl.org/Documentation/OpenGL12. html. Die Spezifikation der OpenGL Version 1.2. [35] . . . . OpenGL - High Performance 2D/3D Graphics. http://www.opengl.org/. Die offizielle Homepage zu OpenGL. [36] . . . . Perl/GTK. http://www.gnome.org/applist/view.php3?name=Perl/GTK. Interface für GTK[22]. Wurde nicht weiter ausprobiert. Perl- [37] . . . . PyOpenGL. http://PyOpenGL.sourceforge.net. OpenGL-Bindings für Python. Zur Bildschirmausgabe benutzt man entweder glut oder hat ein grafisches Toolkit, welches einen OpenGL-Kontext bereitstellen kann (zum Beispiel GTK/GtkGlArea[9] oder Tk/Togl[49].). [38] . . . . Python Documentation. http://www.python.org/doc/. Englische Dokumentation zu Python. [39] . . . . Python Documentation German. http://www.python.org/doc/NonEnglish.html#german. Deutsche Dokumentationen zu Python. [40] . . . . Python Language Website. http://www.python.org. Interpretative Sprache mit Klassensystem. Die Scripte können mit Threads arbeiten. Leicht zu erweitern durch eigene Datentypen. Oft wird Tcl/Tk mit geliefert, welches dann, über TkInter angesteuert, die Erstellung grafischer Anwendungen ermöglicht. (Die etwas dürftige Ausstattung des Tk kann mittels der Python megawidgets[8] wesentlich verbessert werden. [41] . . . . Python Tutorial. http://www.python.org/doc/current/tut/tut.html. Ein Einführung in Python. [42] . . . . QT. http://www.trolltech.com/products/qt/. Ein grafisches Toolkit, welches insbesondere auf Linux mit dem KDE weite Verbreitung gefunden hat. Leider ist die Windows-Version nicht frei erhältlich, so das es hier nicht zur Anwendung kommt. [43] . . . . SaX - Simple API for XML. http://www.megginson.com/SAX/. Ein Quasi-Standard für ein Interface zum ereignisgesteuerten Verarbeiten von XML-Daten. Bei Erkennen von Sprachelementen z.B. Tags oder Entities werden benutzerdefinierte Funktionen aufgerufen. Derzeit ist eine zweite Version des Standards in Arbeit. [44] . . . . Tcl/Tk. http://dev.scriptics.com/software/tcltk/. Tcl/Tcl ist eine Scriptsprache, die mit Tk ihr eigenes Grafik-Toolkit mitbringt. Es ist für alle möglichen Systeme verfügbar . [45] . . . . The GUI Toolkit, Framework Page. http://www.theoffice.net/guitool. Eine Übersicht über so ziemlich alle existierende GUI Toolkits und deren wichtigste Eigenschaften, wie zum Beispiel Platfformen, Lizenzen und so weiter. 112 Quellenverzeichnis [46] . . . . The Mesa 3D Graphics Library. http://mesa3d.sourceforge.net/. Freie OpenGL Implementation, verfügbar für UNIX, Windows, DOS, 3Dfx und alles was sonst noch eine irgendwie geartete Grafikausgabe ermöglicht. [47] . . . . The Visualization Toolkit. http://www.kitware.com/vtk.html. Toolkit zur Visualisierung von 3D-Objekten, ist auch als Buch erhältlich. [48] . . . . Tix Home Page. http://tix.mne.com/htdocs/tix/index.html. Erweiterung des grafischen Toolkits von Tk durch Trees, Notebooks und bessere Listen. Offensichtlich seit längerer Zeit (1997) keine Weiterentwicklung. [49] . . . . Togl - a Tk OpenGL widget. http://togl.sourceforge.net. Ein Tk-Widget, welches ein OpenGL-Kontext bereitstellt. Kann in Version 1.5 unter Win32 noch keine Displaylisten mit anderen Togl-Widgets teilen. [50] . . . . VRML 200x. http://www.web3d.org/TaskGroups/x3d/specification/index. html. VRML 200x ist der zukünftige Nachfolger von VRML 97. Es ist allerdings noch in Entwicklung. Einige neue Features sind unter anderem die Unterstützung von Multi-Texturing auf grafischer Seite und XML als neues Dateiformat. [51] . . . . VRML 97. http://www.web3d.org/technicalinfo/specifications/vrml97/ Virtual Reality Modelling Language – VRML dient zur Beschreibung von 3DObjekten. Es ist möglich ECMA-Skriptsequencen einzubauen, die durch entsprechende Ereignisse aktiviert werden. Dadurch können aktive Welten beschrieben werden. index.htm. [52] . . . . Webots. http://www.cyberbotics.com/webots/. Der einzige bisher verfügbare kommerzielle Khepera-Simulator. [53] . . . . WxWindows. http://www.wxwindows.org. Dies ist ein grafisches Toolkit, welches auf den bereits auf dem System vorhandenen Toolkits (Motif oder GTK bzw. MFC) aufsetzt. (Dann kann man aber auch gleich GTK benutzen.) Es basiert vollständig auf C++ und bietet unter anderem auch eine Python-Anbindung. [54] . . . . XML - Extended Markup Language. http://www.w3c.org/XML/. Spezifikation des XMLStandards. [55] . . . . XVT DSC/DSC++. http://www.xvt.com/docsnf/nfdsc.html. Dies ist ein grafisches Toolkit, welches unter anderem an der HTW im Fach „Grafische Benutzeroberflächen“ benutzt wird. Da es kommerziell vertrieben wird, wird es offensichtlich nicht für freie Software benutzt, zumindest ist mir derzeit keine solche bekannt. 113