Im Folgenden finden Sie eine genauere Beschreibung unserer Kontrollsoftware. Der Text besteht aus Auszügen mehrerer Statusberichte.
Aufbau
Die Software ermöglicht es in erster Linie, Variablen und damit Daten des Roboters auf dem PC sichtbar und editierbar zu machen, was erstens sehr hilfreich für die Entwicklung unseres Robotersystems ist und zweitens die Übermittlung von Befehlen zum Roboter ermöglicht, was ja trotz aller Autonomie des Roboters immer noch notwendig ist, um dem Roboter zum Beispiel den Zielpunkt zu nennen. Außerdem ist es mit der Software möglich, zwischen mehreren unabhängigen Hardware-Modulen Daten auszutauschen. Somit können beispielsweise zwei verschiedene Roboter miteinander vernetzt werden.
Die Software ist so aufgebaut, dass sie mit jeder beliebigen Roboterelektronik kommunizieren kann. Dies ermöglicht die Plugin- und Treiber-Funktionalität.
Um eine Hardware mit der Software anzusteuern, ist es lediglich nötig, einen Treiber zu programmieren, der Variablenwerte über die Kommunikationsschnittstelle (zum Beispiel der seriellen Schnittstelle) zur Software weiterleitet und umgekehrt Befehle und Daten über die Schnittstelle zum Roboter schickt. Somit ist die Software selber von der Hardware unabhängig und kann mit fast jedem erdenklichen Mikrocontroller verwendet werden.
Treiber und Plugins
Es wird zwischen Treibern und Plugins unterschieden. Treiber stellen die Verbindung zwischen der Soft- und Hardware dar. Plugins hingegen können alle anderen Aufgaben übernehmen, wie zum Beispiel eine Editierung der Roboterkarte. Auch die Anzeigefelder der Displaybar sind spezielle Plugins. Treiber und Plugins werden als ActiveX-DLLs kompiliert und beim Starten der Software registriert und in die Software eingebunden. Ihr Grundgerüst besteht aus einem Klassenmodul, das bestimmte Funktionen und Prozeduren haben muss, da diese von der Steuersoftware aus aufgerufen werden.
Für Plugins sind folgende nötig:
- Function INIT(ByRef Door As Object, _
- ByRef ProfileName As String) As Boolean
- 'Initialisierungsfunktion: wird aufgerufen, wenn
- 'Plugin eingebunden wird (beim Startvorgang des
- 'CDKs). Das Plugin bekommt mitgeteilt, was für ein
- 'Name ihm zugeteilt wurde. Außerdem wird das
- 'Door-Objekt übergeben.
- Function INFO() As String
- 'Gibt die Bezeichnung des Plugins zurück.
- Sub ACTIVATION()
- 'Wird ausgeführt, wenn der Menüpunkt des Plugins
- 'angeklickt wird
Und bei Treibern:
- Function INIT(ByRef Door As Object, _
- ByRef DriverName As String) As Boolean
- 'Initialisierungsfunktion: wird aufgerufen, wenn
- 'Plugin eingebunden wird (beim Startvorgang
- 'des CDKs).
- Function INFO() As String
- 'Gibt die Bezeichnung des Treibers zurück
- Function START() As String
- 'Wird aufgerufen, wenn die Interfaces aktiviert
- 'werden sollen.
- Function STOPP() As String
- 'Wird aufgerufen, wenn die Interfaces deaktiviert
- 'werden sollen.
- Function VARUBOUND() As Integer
- 'Gibt die Anzahl der Variablen zurück, die der
- 'Treiber registrieren will.
- Function SETVAL(varname, value) As Boolean
- 'Wird aufgerufen, wenn der User den Wert einer
- 'Variable dieses Treibers ändert.
Wird ein Treiber bzw. ein Plugin initialisiert, wird in ihm die INIT-Funktion aufgerufen. Dabei wird ihm eine Referenz auf das Door-Objekt übergeben, über das mit der Software kommuniziert werden kann. Das Objekt beinhaltet folgende Funktionen:
- Public Sub resetScriptControl()
- 'Kompiliert den User-Quellcode neu.
- Public Function MakeLeftBarTable(_
- InsertObject As Form)
- 'Erstellt einen Table in der linken Leiste (Das macht
- 'z.B. der Datenbrowser, damit er dort links angezeigt
- 'wird).
- Public Sub RemoveLeftBarTable(_
- Table As Object)
- 'Der Table kann auch wieder entfernt werden.
- Public Sub AddMeToDataChangedEventList(
- _Objekt As Object)
- 'Klassen, die in dieser Liste eingetragen sind,
- 'werden informiert, wenn sich etwas in AllData
- '(alle Variablen) ändert. Fenster, die also einen
- 'Variablenwert anzeigen, können die Anzeige
- 'somit aktualisieren.
- Public Sub RemoveMeFromDataChangedEventList( _
- Objekt As Object)
- 'Und wieder Löschen des Eintrags.
- Public Sub MakeMeToDragdropTable(_
- ByRef FormObjekt As Object, _
- ByRef OverObjekt As Object, _
- ByVal DataType As String)
- 'Hier kann ein DragAndDrop-Zielobjekt angemeldet
- 'werden (siehe unten).
- Public Sub UndoMakeMeToDragdropTable( _
- ByRef FormObjekt As Object, _
- ByRef OverObjekt As Object, _
- ByVal DataType As String)
- 'Abmelden des Tables.
- Public Sub MakeMeToChild(FormObjekt As Object)
- 'Ein Plugin kann mit dieser Funktion erreichen, dass
- 'ein Fenster von ihm als Child des CDKs angezeigt
- 'wird.
- Public Sub drag(ByVal Data As Variant, _
- DataType As String)
- 'Startet den DragAndDrop-Vorgang
- Public Sub drop()
- 'Und beendet ihn wieder. Diese Sub ist im Prinzip
- 'ohne Relevanz, da das beim lösen der Maustaste
- 'automatisch passiert. Aber man kann den Vorgang
- 'hiermit abbrechen.
- Public Function getDragedData() As Variant
- 'Gibt das Datum zurück, das verschoben wird.
- Public Function getDragedType() As String
- 'Gibt den Datentyp des Datums zurück, das
- 'verschoben wird.
- Public Function getDragStatus() As Boolean
- 'Ist gerade ein DragAndDrop-Vorgang aktiv?
- Public Sub MsgBar(ByVal MsgBarStyle As Integer, _
- ByVal Content As String)
- 'Zeigt eine Nachricht in der Statusleiste an.
- Public Function addVar( _
- ByVal Group As String, _
- ByVal VarName As String) _
- As Boolean
- 'Fügt einer Datengruppe in AllData eine Variable
- 'hinzu.
- Public Function addGroup( _
- ByVal GroupName As String) _
- As Boolean
- 'Fügt zu AllData eine neue Gruppe hinzu.
- Public Function getData(ByVal Group As String, _
- ByVal Name As String) _
- As Variant
- 'Gibt den Wert einer Variable in AllData zurück.
- Public Function setData(ByVal Group As String, _
- ByVal Name As String, _
- ByVal value As Variant) _
- As Boolean
- 'Setzt den Wert einer Variable in AllData.
- Public Function getGroupNames( _
- ByVal Index As Integer) _
- As String
- 'Gibt die Gruppennamen in AllData zurück. Nur
- 'sinnvoll in Verbindung mit einer For-Schleife.
- Public Function getVarNames( _
- ByVal GroupIndex As Integer, _
- ByVal VarIndex As Integer) _
- As String
- 'Gibt die Variablennamen aus einer Gruppe in AllData
- 'zurück Funktioniert wie vorige Funktion.
- Public Function getVarsUBound( _
- ByVal Group As String) _
- As Integer
- 'Gibt die Anzahl der Variablen in einer
- 'Gruppe zurück.
- Public Function getGroupsUBound() As Integer
- 'Gibt die Anzahl der Gruppen in AllData zurück.
- Public Function getGroupIndexByName( _
- ByVal Name As String) _
- As Integer
- 'Gegenstück zu getGroupNames.
- Public Function getVarIndexByName( _
- ByVal GroupName As String, _
- ByVal VarName As String) _
- As Integer
- 'Gegenstück zu getVarNames
Weiter unten finden Sie Code-Beispiele für einen Treiber.
Benutzeroberfläche
Mit dem Startbildschirm lässt sich ein neues Profil anlegen, in dem die zu verwendende Treiber sowie Fensterpositionen etc. eingespeichert werden. Dieses Profil lässt sich auch in diesem Fenster wieder auswählen und schon hat man alle Daten des entsprechenden Roboters auf dem Bildschirm. Normalerweise reicht ein Profil jedoch aus.
Und der Hauptbildschirm:
Links im Bild sieht man den "Datenbrowser". Er enthält alle Variablen nach Gruppen sortiert. Er ist als Plugin implementiert und damit (wie alle anderen Plugins auch) vom eigentlichen System unabhängig kompiliert und greift über die Interface-Klasse mit dem bezeichnenden Namen "Door" auf die Daten zu. Plugins werden normalerweise als ganz normale Fenster angezeigt Beim Starten hat sich der Datenbrowser jedoch mit der Funktion "MakeLeftBarTable" in die linke Leiste "gepflanzt". Diese Möglichkeit ist sehr praktisch, da auf diesem Wege oft verwendete Plugins nicht in dem Fenstergewirr untergehen sondern übersichtlich einsortiert werden.
Unten ist eine Message-Bar zu finden, die sich genauso wie die Message-Boxen verwenden lässt. Somit können Meldungen angezeigt werden, die eine geringere Wichtigkeit haben und nicht mit "Ok" bestätigt werden müssen.
Rechts ist die Displaybar zu sehen. Über das Menü lassen sich neue Displays hinzufügen und entfernen, per Drag and Drop werden sie dann mit Variablen verlinkt. Displays sind Plugins, die sich jedoch nicht im Standard-Verzeichnis sondern in einem speziellen Unterordner befinden und beim Start der Software als Display initialisiert werden. Von einem Display können mehrere Instanzen angezeigt werden. In dem Bild oben handelt es sich bei der zweiten und dritten Analoganzeige um zwei Instanzen desselben Displays, die mit zwei verschiedenen Variablen verlinkt sind.
Das Fenster im Hintergrund enthält den Userquellcode. Über diesen Code können der Datenaustausch zwischen Roboter und PC kontrolliert und die Beziehungen zwischen mehreren Variablen festgelegt werden. Ändert sich der Wert einer Variablen, wird hier ihre Ereignisprozedur aufgerufen. Der Quellcode, der in dieser Prozedur steht, wird also immer bei einer Änderung des Variablenwertes ausgeführt und kann darauf reagieren. Somit kann zum Beispiel gezählt werden, wie oft eine Variable geändert wurde oder einer Variable kann die Differenz von zwei anderen Variablen zugewiesen werden. Die Anwendung hängt somit natürlich von den Daten ab. Ein anderes Beispiel wäre eine Anzeige, die Alarm gibt, wenn die Akkuspannung des Roboters einen bestimmten Wert unterschreitet.
Drag and Drop
Ein wichtiges Ziel des CDKs ist es, eine einfache und komfortable Möglichkeit zu schaffen, Werte von Mikrocontrollern in Echtzeit zu verwalten und zu beobachten. Eine wichtige Funktion hierfür ist Drag and Drop. Sie soll es zum Beispiel ermöglichen, Variablen mit der Maus aus dem Datenbrowser zu nehmen und mit einem Display zu verlinken.
Die Drag and Drop-Funktionen sind auch in der "Door"-Klasse zu finden. Der Datenbrowser startet den Drag-Vorgang, wenn auf ein Element geklickt wird. Der Sub Door.Drag wird einmal das Datum (Einzahl von Daten), nämlich der gesamte Pfad (beispielsweise "Treiber1.Var1"), übergeben. Außerdem muss der Typ angegeben werden. In diesem Fall handelt es sich um "cdk_link", da wir ja eine Variable mit einem Display verlinken wollen.
Das Display wird in der Regel ein Plugin sein, da das CDK selbst keine Displays enthält. Bei der Initialisierung muss es sich als DragAndDrop-Table anmelden. Das heißt, dass es einen bestimmten Typ von Daten annehmen kann. Door.MakeMeToDragdropTable erledigt dies. Neben Anderem wird der Typ übergeben, in diesem Fall wie gesagt "cdk_link". Dadurch wird gleichzeitig verhindert, dass irgendein anderes Datenformat übergeben wird, mit dem das Display nichts anfangen kann.
Wenn man jetzt mit der Maus aus dem Datenbrowser eine Variable "packt", wird neben dem Cursor ein entsprechendes Icon angezeigt, das je nach Datentyp anders ist. Dieses Icon macht mit einem eventuell erscheinenden Kreuz kenntlich, ob das Datum an der momentanen Mausposition abgelegt werden kann.
Plugins können also über "Door" Drag and Drop-Vorgänge steuern. Die verfügbaren Typen sind dabei unbegrenzt. Vom CDK selbst wird nur "cdk_link" verwendet, aber Plugins können untereinander auch andere Typen verwenden. Als Icon wird dann ein Standardlogo verwendet, es sei denn, es werden in das Verzeichnis "Images" zwei entsprechende bmp-Dateien angelegt. Für "cdk_link" liegen dort bereits "cdk_link_i.bmp" und "cdk_link_p.bmp". Das CDK sucht also erst in diesem Verzeichnis nach den Dateien (Typ + "_i" bzw. "_p", stehend für "impossible" und "possible"), bevor es das Standardlogo verwendet.
Beispielcode für einen Treiber
Dieser Beispiel-Quellcode zeigt, wie ein mit Visual Basic programmierter Treiber für eine automatische Tür aussehen könnte.
- Option Explicit
- '===========================================================
- ' Dieses Klassenmodul ist mit dem CDK
- ' verknüpft und muss unbedingt vorhanden sein
- '===========================================================
- '-----------------------------------------------------------
- 'HINWEIS: Die Köpfe der folgenden Funktionen/Subs dürfen
- ' nicht verändert werden, da das CDK auf sie zugreift.
- '-----------------------------------------------------------
- Function INIT(Door As Object, DName As String) As Boolean
- 'Initialisierungsfunktion: wird aufgerufen, wenn Plugin
- 'eingebunden wird (beim Startvorgang des CDKs)
- Set TheDoor = Door
- DriverName = DName
- INIT = True
- End Function
- Function INFO() As String
- 'Gibt die Bezeichnung des Plugins zurück - unbedingt
- 'einen individuellen und aussagekräftigen Namen wählen!
- INFO = "Schiebetür1"
- End Function
- Function START() As String
- 'Wird aufgerufen, wenn die Interfaces aktiviert
- 'werden sollen
- 'Hier wird über die serielle
- 'Schnittstelle eine Signallame
- 'eingeschaltet:
- Seriell.send("lamp on")
- End Function
- Function STOPP() As String
- 'Wird aufgerufen, wenn die Interfaces deaktiviert
- 'werden sollen
- Seriell.send("lamp off")
- End Function
- Function VARNAMES(number) As String
- 'Gibt die Namen der Variablen zurück, die auf dem
- 'Mikrocontroller vorhanden sind.
- If number = 0 Then
- VARNAMES = "Signallampe"
- Else
- VARNAMES = "Status"
- End If
- End Function
- Function VARUBOUND() As Integer
- 'Gibt die Anzahl der Variablen zurück, die auf dem
- 'Mikrocontroller gespeichert sind.
- VARUBOUND = 1 'Null-basierter Index, also 2 Variablen
- End Function
- Function SETVAL(varname, value) As Boolean
- 'Wird aufgerufen, wenn der User den Wert einer Variable
- 'dieses Treibers ändern möchte.
- If varname = "Signallampe" Then
- If value = "ON" Then
- Seriell.send("lamp on")
- Else
- Seriell.send("lamp off")
- End If
- Else
- If value = "OPEN" Then
- Seriell.send("door open")
- Else
- Seriell.send("door close")
- End If
- End If
- SETVAL = True
- End Function
- '===========================================================
- Option Explicit
- '===========================================================
- ' Dieses Modul dient der öffentlichen
- ' Bereitstellung der Door-Klasse u.Ä.
- '===========================================================
- Public TheDoor As Object
- Public DriverName As String
Die Variablen dieses Beispieltreibers sind beide nur dazu da, um von der Kontrollsoftware geschrieben zu werden. Es ist jedoch keine Variable vorhanden, deren Wert der Treiber selbst ändert. Ein Beispiel dafür wäre ein Schalter, der an der Tür angebracht ist. Der Treiber könnte den Schalter ständig überprüfen und den Status (also "an" oder "aus") in eine Variable schreiben. Dazu wäre nur eine Schleife nötig, die bei einer Änderung den neuen Wert mit dem Aufruf "TheDoor.setData(DriverName, Status)" an die Kontrollsoftware übergibt.

