Die Steuerung der Roboter erfolgt über ein in Java programmiertes Framework. Hiermit lassen sich die Prozesse zur Steuerung auf nahezu beliebig viele Rechner unterschiedlichster Art verteilen, zusätzliche Prozesse recht einfach integrieren und mehrere Roboter steuern. Die Roboter mit ihren Motoren, Servos und Sensoren werden über ein XML-File konfiguriert und beim Systemstart geladen. Hierdurch sind Umbauten und Erweiterungen schnell in die Steuerung zu integrieren. Die einzelnen Objekte haben folgende Funktionen: Node:
Ein Mode ist ein physikalischer oder virtueller Computer im Netzwerk. Er wird mit Namen, Hostname oder Adresse so wie der Anzahl der Signalworker konfiguriert. Ein Signalworker ist hierbei ein Prozess, der Signale entgegen nimmt und den einzelnen Detailprozessen wie einem Gehirnabschnitt oder einem Sensor zuordnet. Es können nahezu beliebig viele Nodes konfiguriert werden.
Port:
Ein Port ist ein physikalischer oder virtueller Anschluss an einem Node. Dies kann beispielsweise eine serielle Schnittstelle oder bei der Raspberry Pi der GPIO-Port sein. In der aktuellen Umsetzung sind der GPIO-Port und der I2C-Port der Raspberry Pi implementiert. Weitere können nach Bedarf recht einfach folgen. Diese müssen nur als Implementation des Interfaces IPort erstellt werden – und schon können diese in Konfigurationen für Roboter verwendet werden.
Körper:
Der Körper ist als zentrale Klammer für eine Hardware eines Roboter gedacht. Hierbei ist mit der Zuweisung zu einem Node nur der ausführende Node des Hintergrundprozess für diesen Körper festgelegt. Die einzelnen Motoren und Sensoren können durchaus durch Ports an anderen Nodes gesteuert werden. Ein Roboter mit mehreren Computern wird also unterstützt. Ebenso können wie es bei den Robotern von der Band der Fall ist mehrere Roboter mit je einem eigenen Node in einer gemeinsamen Konfiguration abgebildet werden. Dies ermöglicht eine einfach umzusetzende zentrale Steuerung der Roboter.
Körperteil:
Ein Körperteil ist nur eine Gliederung um den Körper in einzelne Abschnitte wie Arm, Kopf oder Beine zu gliedern. Hier ist eine nahezu unbegrenzte Schachtlung möglich. Beispiel: Kopf->Mund, Augen, Hals.
Motoren und Sensoren:
Dies sind die eigentlichen Bauteile des Robotors. Neben einfachen Motoren sind derzeit zwei verschiedene Servos implementiert. Zum einen die per I2C gesteuerten “OpenServo” als auch einfache per PWM gesteuerte Servos. Als Sensor ist bisher nur ein einfacher Temperatursensor der per I2C angeschlossen wird als Test implementiert.
Reiz:
Reize sind eine Besonderheit des Frameworks und bilden Überwachungsprozesse ab. Beispiel: die Stromstärke eine Motors soll 2 Ampere nicht überschreiten. Hier kann ein Reiz definiert werden der in einem eigenen Hintergrundprozess regelmäßig die Stromstärke überprüft. Sobald diese 2 Ampere übersteigt sendet der Prozess selbstständig ein Signal an den konfigurierten Gehirnabschnitt wo dann entsprechende Reaktionen umgesetzt werden können. Der Hintergedanke dieses Objekts ist es, das eigentliche Gehirn, in dem später auch KI-Ansätze umgesetzt werden sollen nur bei Veränderungen (z.B. Berührung eine Objekts durch die Hand) oder laufenden Alarmzuständen (ähnlich wie Schmerzen oder Hunger) aktiv werden muss. Die Überwachung des Körpers und somit die Erzeugung der Reize kann auf einen anderen Computer ausgelagert werden und muss zudem nicht im Gehirnprozess implementiert werden.
Gehirnabschnitt:
Ein Gehirnabschnitt ist eine Logik oder KI-Implementation, die auf bestimmte Signale oder Reize reagiert und diese entweder an weitere Gehirnabschnitte weiterleitet oder dem Körper bestimmte Steuerbefehle erteilt. So ist derzeit der Prozess der MidiBridge als Gehirnabschnitt implementiert. Er empfängt das Signal einer Note, prüft ob diese einem Bewegungsablauf eines Roboter zugeordnet ist, und sendet ein Signal an den entsprechenden Roboter zum Starten des Bewegungsablaufs.
Individualprozess:
Ein Individualprozess ist ähnlich wie ein Gehirnabschnitt. Jedoch hat dieser weniger Anforderungen an die Implementation. Er muss beispielsweise nicht zwingend eine Ein- und Ausgangsqueue für Signale besitzen.
Beispiel einer Minimalkonfiguration
Hier die Minimalkonfiguration für Ralph mit entsprechenden Erklärungen: <?xml version=”1.0″ encoding=”UTF-8″ standalone=”yes”?> <!– Das Konfigurationsobjekt roberryConfig wird in einer neuen Version des Frameworks noch umbenannt. Es ist geplant dies umzubenennen –> <roberryConfig>
<!– Hier wird ein ersten Node konfiguriert. Der Node ist die in Ralph eingebaute Raspberry Pi. Der Port ist der Port des installierten Tomcat-Servers, der Servicename der Name der auf dem Tomcat laufenden Webapp –> <nodeConfig>
<anzahlsignalworker>5</anzahlsignalworker> <hostname>Ralph001</hostname> <name>Ralph001</name> <port>8080</port> <servicename>Roberry</servicename> <typ>Standard</typ>
</nodeConfig>
<!– Hier werden die Ports von Ralph konfiguriert. Es wird der I2C-Bus 1 und der GPIO-Port verwendet. Read- and Writeretry des I2C-Bus beziffert die Anzahl der Versuche einen Wert zu einem I2C-Device zu senden bzw. einen Wert zu lesen. –> <portConfig>
<typ>I2cPort</typ> <nodename>Ralph001</nodename> <hardwareport>1</hardwareport> <name>I2C01RALPH</name> <readretry>10</readretry> <writeretry>10</writeretry>
</portConfig>
<portConfig>
<typ>GpioPort</typ> <nodename>Ralph001</nodename> <name>GPIORALPH</name>
</portConfig>
<!– Die Körper-Konfiguration von Ralph enthält vier Körperteile: Drehteller (Die Drehscheibe auf der Ralph steht), Den Arm links, den Arm rechts und den Kopf. Der Hauptprozess wird auf dem Node Ralph001 ausgeführt. Der Pfad Ablaufsteuerung ist der Pfad, in dem die XML-Files für Bewegungsabläufe gespeichert sind. Mehr hierzu später. Das Flag Autostart gibt an ob der Hauptprozess des Körpers automatisch gestartet werden soll. –> <koerperConfig>
<typ>Default</typ> <name>Ralph</name> <nodename>Ralph001</nodename> <pfadAblaufsteuerung>/home/pi/Share/Ablaufsteuerung</pfadAblaufsteuerung> <autostart>true</autostart>
<!– Der erste Körperteil Drehteller enthält nur einen Motor vom Typ OpenServo. Dieser ist am I2C01RALPH angeschlossen, weiter oben definiert ist. Der Servo hat die Adresse 0x40. –> <koerperteil>
<typ>Default</typ> <name>Drehteller</name>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x40</adresse> <name>Drehen</name>
</motor>
</koerperteil>
<koerperteil>
<typ>Default</typ> <name>ArmL</name>
<!– hier ist erstmalig ein GPIO-Pin verwendet. Es ist der Pin 0 an dem GIPO-Port GPIORALPH, der weiter oben definiert ist. –> <gpio>
<typ>GpioOut</typ> <port>GPIORALPH</port> <channel>0</channel> <name>LaserL</name>
</gpio>
<!– Die weitere Konfiguration besteht ausschließlich aus den weiter oben erklärten Elementen. Hier wird auf eine weitere Kommentierung verzichtet. –> <motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x11</adresse> <name>Schulter01</name>
</motor>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x12</adresse> <name>Schulter02</name>
</motor>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x13</adresse> <name>ArmDrehen</name>
</motor>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x14</adresse> <name>Ellenbogen</name>
</motor>
</koerperteil> <koerperteil>
<typ>Default</typ> <name>ArmR</name>
<gpio>
<typ>GpioOut</typ> <port>GPIORALPH</port> <channel>1</channel> <name>LaserR</name>
</gpio>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x21</adresse> <name>Schulter01</name>
</motor>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x22</adresse> <name>Schulter02</name>
</motor>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x23</adresse> <name>ArmDrehen</name>
</motor>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x24</adresse> <name>Ellenbogen</name>
</motor>
</koerperteil>
<koerperteil>
<typ>Default</typ> <name>Hals</name>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x31</adresse> <name>Nicken</name>
</motor>
<motor>
<typ>OpenServo</typ> <port>I2C01RALPH</port> <adresse>0x32</adresse> <name>Drehen</name>
</motor>
</koerperteil>
</koerperConfig>
</roberryConfig> Das ist schon alles was für Ralph konfiguriert werden muss. Dem Framework ist jetzt bekannt das es einen Node namens Ralph001 gibt der über zwei Ports und einige Servos und GPIO-Pins verfügt. Hiermit kann ein Gehirnabschnitt oder ein Individualprozess auf diese zugreifen. Dieser Gehirnabschnitt muss nicht auf demselben Node laufen da die Kommunikation komplett über Webservices läuft und die Adresse des Webservice von Ralph bekannt ist. Für einen recht experimentellen KI-Ansatz kann eine KI-Komponente zudem jetzt auf alle Metadaten der Konfiguration von Ralph zugreifen. Es ist also denkbar das eine KI zum lernen einfach mal an einen Servo den Befehl “gehe zu Position 400” sendet und dann mal guckt was passiert. Wenn die KI gut ist sollte sie dann nach und nach lernen sich gezielt zu bewegen 😉
Abspielen eines Bewegungsablaufs
Die Funktion zum “abspielen” eines Bewegungsablaufs ist in den Prozess des Körpers integriert. In der Konfiguration des Körpers ist ein Verzeichnis konfiguriert, in dem die XML-Files zur Konfiguration von Bewegungsabläufen hinterlegt sind. Diese sind in zwei Teile aufgeteilt: Definition von Positionen In den Files zur Definition von Positionen können die Positionen beliebig vieler Servos, Motoren und GPIO-Ports gespeichert werden: <!– Die Definition von einer Sammlung von Positionen einzelner Servos oder GPIO-Pins die gleichzeitig aufgerufen werden sind in dem Step-Objekt zusammen geführt –> <step>
<!– Eine Position gibt immer den Zusand exakt eines Objekts an. Hier dem GPIO-Pin für die Laserdioden der rechten Hand –> <position>
<!– Das Objekts wird einfach dem Pfad entlang der Konfiguration des Körpers (siehe oben) mit Punkten getrennt angegeben. Ist an dem Node nur ein Körper aktiv (Hauptprozess des Körpers darauf konfiguriert) kann auf diesen per “defaultkoerper” zugegriffen werden. –> <objektpfad>defaultkoerper.ArmR.Laser</objektpfad>
<!– bei GPIO-Pins (Digital bedeutet 0 = LOW und > 0 = HIGH –> <power>1</power>
</position>
<position>
<!– hier ein Beispiel für die Komplette Pfadangabe eines Körpers (notwendig wenn mehrere Körper durch einen Node gesteuert werden) –> <objektpfad>koerper.Ralph.ArmR.EllenbogenR</objektpfad> <!– hier die Position in die der Servo fahren soll –> <pos>520</pos> <!– wird der Wert über eine Rampe in einer bestimmten Zeit angefahren kann hier die angenommene Startposition angegeben werden. Bei “-1” wird die aktuelle Position des Servos verwendet –> <posStart>-1</posStart> <!– mit Power kann die maximale PWM-Pulsbreite und somit die Geschwindigkeit des Servos angegeben werden. Bei “-1” wird dieser Wert vom Bewegungsablauf (siehe weiter unten) übernommen. –> <power>-1</power> <!– Rampe gibt an ob der Servo schrittweise von der posStart zu der Zielposition geführt wird. Bei “-1” wird diese Konfiguration von dem Bewegungsablauf übernommen, bei “0” wird direkt ohne eine Schrittweise Annäherung die Zielposition angefahren, bei “1” ist die weiter unten beschriebene Rampenfunktion aktiv. –> <rampe>-1</rampe> <!– time gibt die Zeit der Rampe an wenn diese aktiv ist. Wenn nicht ist dies die Wartezeit bevor der nächste Step ausgeführt wird. “-1” bedeutet hier wieder das der Wert von dem weiter unten beschriebenen Bewegungsablauf übernommen wird. –> <time>-1</time>
</position>
</step> Eine soleche Positionsdatei kann über ein Browserinterface einfach erstellt werden. Hierfür kann der Roboter per Hand auf die gewünschte Position gesetzt werden oder mit einer einfachen Oberfläche die Servos auf Position gefahren werden. In Folge nur noch einen Namen für diese Stellung eingeben und SavePos klicken – und schon ist die Step-XML-Datei fertig.
Definition eine Bewegungsablaufs
Mit einem Bewegungsablauf können veschiedene Positionen nacheinander angefahren werden. Für jeden Step kann eine Wartezeit bis zum nächsten Step konfiguriert werden. Die Positionen der einzelnen Servos können entweder in dem XML-Dokument des Bewegungsablaufs festgelegt werden oder alternativ zuvor gespeicherte Step-Dateien (siehe oben) aufgerufen werden. Die folgende Datei stetzt den Roboter auf die Startposition Hierfür werden den Servos 3000ms = 3 Sekunden Zeit gegeben. In Folge wird der Ellenbogen des rechten Arms auf die Position 320 gefahren. Weitere 1000ms später werden alle Motoren und Servos des Roboters ausgeschaltet. <?xml version=”1.0″ encoding=”UTF-8″ standalone=”yes”?> <!– Hier wird ein Bewegungsablauf definiert –> <bewegungsablauf>
<!– bei “true” werden nach der Ausführung des letzten Steps alle Motoren des Roboter ausgeschaltet. Bei false bleiben diese aktiv. –> <alloff>true</alloff> <!– Geschwindigkeit (MaxPWM) mit der alle Servos betrieben werden soweit beim Step oder der Position nichts anderes angegeben ist –> <power>200</power> <!– gibt an ob die Rampenfunktion aktiv ist. (“1” = die Position wird in Einzelschritten von der in rampetimebase konfigurierten Länge angefahren, “0” = keine Rampenfunktion) –> <rampe>0</rampe> <!– Zeitbasis für die einzelschritte der Rampenfunktion in ms –> <rampetimebase>500</rampetimebase> <!– Dauer eines Steps im Bewegungsablauf soweit im Step selber nichts anderes angegeben ist –> <time>3000</time> <step>
<!– hier werden die Positionen aus der Step-Datei 00_Start.xml geladen soweit diese im Unterverzeichnis Steps vorhanden ist. –> <name>00_Start</name>
</step>
<step>
<!– hier wird ausnahmsweise eine Position direkt in dem Bewegungsablauf gesetzt. Empfolen wird imm er die Verwendung einer Step-Datei da dies übersichtlicher ist und die Positionen in verschiedenen Bewegungsabläufen wiederverwendet werden können. –> <position>
<objektpfad> defaultkoerper.ArmR.EllenbogenR </objektpfad> <pos>320</pos> <time>1000</time>
</position>
</step>
</bewegungsablauf> In dieser Art können einfach und schnell Bewegungsabläufe zusammengestellt werden die per Browseroberfläche oder Webservice gestartet werden können.