Konzeption und Aufbau eines modular erweiterbaren

Transcription

Konzeption und Aufbau eines modular erweiterbaren
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 <R.Liebscher@gmx.de>
*/
#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 <R.Liebscher@gmx.de>
*/
#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