Jython

12. Lernfähige Roboter


Ein Roboter kann beliebig viele Informationen speichern und mit Hilfe der gespeicherten Daten lernen selbständig bestimmte Aufgaben zu erledigen. In unseren Beispielen lernt der Roboter eine Hindernisbahn selbstständig durchzufahren.

Beispiel 1: Der Roboter kennt den Weg
Im einfachsten Fall erhält der Roboter alle Informationen im Programm. Er muss eine einfache Hindernisbahn durchfahren, deren gerade Wegstücke immer gleich lange sind. Zusätzlich erhält er in einer Liste memory Informationen, ob er an den Abzweigungen links bzw. rechts abbiegen soll (0 für links, 1 für rechts).

Er geht also die Liste memory = [0, 1, 1, 0] der Reihe nach durch, biegt bei der ersten Abzweigung nach links, bei der zweiten nach rechts usw. Dazwischen fährt er immer die gleiche Anzahl Millisekunden vorwärts.

 

#Ro12a.py

from simrobot import *
#from ev3robot import *

RobotContext.useBackground("sprites/bg.gif")
RobotContext.setStartPosition(310, 470)

robot = LegoRobot()
gear = Gear()
robot.addPart(gear)
tMove = 3200
tTurn = 545
memory = [0, 1, 1, 0]
gear.forward(tMove)

for i in memory:
    if i == 0:
        gear.left(tTurn)
        gear.forward(tMove)
    elif i == 1:
        gear.right(tTurn)
        gear.forward(tMove)     
robot.exit()
Programmcode markieren (Ctrl+C kopieren)

Erklärungen zum Programmcode:

tTurn , tMove : um die Anzahl Millisekunden für die Drehung und Vorwärtsfahren einfacher anpassen zu können, verwendet man Variablen
memory = [0, 1, 1, 0]: Elemente einer Liste werden in den eckigen Klammer aufgezählt
for i in memory(): mit for-Schleife kann die Liste memory "durchgelaufen" werden

 

Beispiel 2: Der Roboter wird durch einen Menschen "angelernt"
Der Roboter kennt den Weg nicht, wird aber in einer "Lernphase" mit Hilfe der Cursortasten LEFT und RIGHT durch den Benutzer gesteuert. Er lernt eine einfache Bahn, deren gerade Wegstücke gleich lang sind und senkrecht auf einander stehen, durchfahren. Dabei muss er sich nur einprägen, ob er links oder rechts abbiegen soll.

Falls der Button LEFT (in der Simulation die Cursortaste LEFT) gedrückt wurde dreht er 90° nach links bei der Taste RIGHT dreht er nach rechts. Bei jeder Drehung speichert er 0 bzw. 1 in der Liste memory, die zu Beginn leer ist. Mit dem Befehl memory.append(0) wird das Element 0 zu der Liste hinzugefügt.

 

Die Lernphase wird mit der Taste ESCAPE beendet (kann auch vor Ende des Parcours sein). Mit Drücken der ENTER-Taste fährt der Roboter anschliessend die Bahn selbständig durch und verwendet dabei die gespeicherten Informationen. Im Simulationsmodus muss der Roboter aber zuerst mit dem Befehl reset an die Startposition zurück versetzt werden. Beim realen Roboter versetzt man den Roboter an die Startposition und drückt danach die ENTER-Taste auf dem Brick. Mit der Taste DOWN wird das Programm beendet.

#Ro12b.py

from simrobot import *
#from ev3robot import *

RobotContext.useBackground("sprites/bg.gif")
RobotContext.setStartPosition(310, 470)
RobotContext.showStatusBar(30)  

robot = LegoRobot()
gear = Gear()
robot.addPart(gear)
tMove = 3200
tTurn = 545
memory = []
gear.forward(tMove)

# learning
while not robot.isEscapeHit():    
    if robot.isLeftHit():        
        gear.left(tTurn)
        memory.append(0)
        gear.forward(tMove)        
    elif robot.isRightHit():         
        gear.right(tTurn)
        memory.append(1)
        gear.forward(tMove)    
    robot.drawString("Memory: " + str(memory), 0, 1)
    print(memory)

# running
while not robot.isDownHit():
    while not robot.isEnterHit():    
        pass
    robot.reset()    
    gear.forward(tMove)
    for i in memory:
        if i == 0:
            gear.left(tTurn)
            gear.forward(tMove)
        elif i == 1:
            gear.right(tTurn)
            gear.forward(tMove)     
robot.exit()
Programmcode markieren (Ctrl+C kopieren)

Erklärungen zum Programmcode:

memory = [] : memory ist zu Beginn eine leere Liste
memory.append(0): beim Linksabbiegen wird eine 0 in die Liste hinzugefügt
memory.append(1): beim Rechtsabbiegen wird eine 1 hinzugefügt
robot.reset: nur im Simulationsmodus, der Roboter wird an die Ausgansposition zurück versetzt

 

Beispiel 3: In der Lernphase wird die Infrarot-Fernsteuerung verwendet
Falls Sie keinen Infrarot Sensor und keine Infrarot-Bedienung haben, können Sie dieses Beispiel weglassen. Es ist nur eine Erweiterung des Beispiels 2.

Der Roboter wird während der "learning"-Phase mit den Tasten "oben links" und "oben rechts" auf der Infrarot-Fernsteuerung angelernt. Analog wie im vorhergehenden Beispiel speichert der Roboter während der "learning"-Phase die Informationen in der Liste memory. Mit dem Drücken der Taste "unten rechts" auf der Fernsteuerung wird die "running"-Phase gestartet. Der Roboter muss vorher an die Ausgangsposition versetzt werden. Danach fährt er selbständig mit Hilfe der Liste memory den Parcours ab. Das Programm kann mit der Taste "unten links" oder mit Drücken des Buttons ESCAPE auf dem Brick beendet werden

 


# Ro12c.py

from ev3robot import *
   
robot = LegoRobot()
gear = Gear()
robot.addPart(gear)
irs = IRRemoteSensor(SensorPort.S1)
robot.addPart(irs)
tMove = 3200
tTurn = 545
memory = []    
gear.forward(tMove)

# learning
while not robot.isEscapeHit() and isRunning:
    command = irs.getCommand() 
    if command == 1: #top left
        gear.left(tTurn)
        memory.append(0)
        print(memory) 
        gear.forward(tMove)
    elif command == 3:#top right
        gear.right(tTurn)
        memory.append(1)
        print(memory) 
        gear.right(tTurn)     
    elif command == 2:#botton left
        gear.stop() 

# running
    #set the robot at the startposition
    elif command == 4: #botton right
        gear.forward(tMove)
        for i in memory:
            if i == 0:
                gear.left(tTurn)
                gear.forward(tMove)
            else:         
                gear.right(tTurn)
                gear.forward(tMove)
    elif command == 2:
        gear.stop()
        isRunning = False                     
robot.exit()
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Erklärungen zum Programmcode:

irs.getCommand: gibt die Nummer des gedrückten Buttons der Fernsteuerung zurück

 

Beispiel 4: Touchsensoren und Timer verwenden, um eine Bahn mit beliebig langen Wegstücken durchzufahren.
Der Roboter lernt selbständig den Parcours durchzufahren, indem er mit Hilfe seines Touchsensors das Ende eines Wegstückes registriert und mit Hilfe eines Timers die Zeit, die er zum Durchfahren der geraden Wegstücke benötigt, misst. Dabei verwendet er folgende Strategie: Am Anfang eines geraden Wegstückes speichert er die aktuelle Zeit in der Variablen startTime, startet den Timer und fährt vorwärts. Wenn er mit dem Touchsensor das Ende eines Wegstückes registiert, berechnet er die Differenz der aktuellen Timerzeit und der Startzeit, korrigiert diese Zeit mit backTime (Zeit, die er benötigt, um eine kurze Strecke zurückzufahren) und speichert das Ergebnis als moveTime.

 


Danach dreht er 90° nach links und fährt vorwärts. Wenn er innert einer kurzen Zeit (hier 2 Sekunden) wieder eine Wand berührt, so hat er die falsche Richtung gewählt. Er dreht um 180° und fährt in der neuen Richtung. Wie in den vorhergehenden Beispielen speichert er 0 für Links- und 1 für Rechtsdrehung. Da er diesmal bei jeden Wegstück zwei Informationen (moveTime und 0 bzw. 1 für die Drehrichtung), besteht die Liste memory aus Zahlenpaaren, die hier als node bezeichnet werden.

Die "learning"-Phase wird mit ESCAPE beendet. Dann wartet der Roboter bis die Taste ENTER gedrückt wurde und fährt die Bahn selbständig durch. Im Realmodus muss der Roboter vor dem Drücken der ENTER-Taste an die Startposition versetzt werden.
(Eine zweite Lösungsvariante siehe unter Bemerkung unten auf dieser Webseite).

# Ro12d.py

from simrobot import *
#from ev3robot import *
import time

RobotContext.useObstacle("sprites/bg2.gif", 250, 250)  
RobotContext.setStartPosition(410, 480)
RobotContext.showStatusBar(30)

robot = LegoRobot()
gear = Gear()
robot.addPart(gear)
ts = TouchSensor(SensorPort.S3)      
robot.addPart(ts)
startTime = time.clock()
moveTime = 0
turnTime = 545
backTime = 600
memory = [] 
gear.forward()

#learning
while not robot.isEscapeHit():
    if ts.isPressed():
        dt = time.clock() - startTime
        gear.backward(backTime)
        if dt > 2:  
            moveTime = int(dt * 1000) - backTime # save long-track time
            node = [moveTime, 0] 
            memory.append(node) 
            gear.left(turnTime) # turning left   
        else:
            memory.pop() 
            node = [moveTime, 1] 
            memory.append(node) 
            gear.right(2 * turnTime) # turning right
        robot.drawString("Memory: " + str(memory), 0, 1)
        print(memory)
        gear.forward()
        startTime = time.clock()   
gear.stop()

#running
while not robot.isDownHit():
    while not robot.isEnterHit():    
        pass
    robot.reset()
    for node in memory:
        moveTime = node[0]
        k = node[1]
        gear.forward(moveTime)
        if k == 0:  
            gear.left(turnTime)            
        elif k == 1:
            gear.right(turnTime)            
    gear.forward() 
robot.exit()
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Erklärungen zum Programmcode:

dt = time.clock() - startTime : die Zeit zwischen zwei aufeinander folgenden Wandberührungen
if dt > 2 : falls die Zeit zur nächsten Wandberührung zu kurz ist, war die Drehung nach links falsch. Mit der Drehung um 180° wird diese zur Drehung um 90° hach rechts korrigiert
int(dt * 1000) -backTime: der Timer gibt die Zeit in Sekunden zurück. Diese wird Millisekunden umgerechnet
node = [moveTime, 0] : die Liste node besteht aus zwei Werten: der erste für Zeit, die der Roboter für einen Weg benötigt, der zweite für links- bzw. rechtsdrehen
memory.append(node) : in die List memory wird jeweils eine Liste mit zwei Werten hinzugefügt
memory.pop(): bei einer Rechtsdrehung muss das letzte Element im Memory zuerst gelöscht werden, da der erste Drehversuch nach links fehlerhaft war

 

Beispiel 5: Lichtsensoren verwenden, um einem Weg mit Abzweigungen zu folgen.
Der Roboter soll die nebenstehende Bahn abfahren. Er bewegt sich auf dem schwarzen Streifen und steuert seine Bewegung mit Hilfe von zwei Lichtsensoren. Wenn er mit beiden Sensoren hell sieht, muss er links oder rechts abbiegen. Er versucht immer zuerst nach Links zu fahren. Wenn er kurz darauf wieder weiss/weiss sieht, war die Entscheidung falsch. Es dreht um 180° und fährt in der neue Richtung zu der nächsten Kreuzung. An jeder Kreuzung speichert er "0" für Links- und "1" für Rechtsabbiegen in der Liste memory.

Die Bewegung auf dem schwarzen Streifen wird in der Funktion keepOnTrack() definiert. An den Kreuzungen verwendet man, wie in der vorhergehenden Aufgabe, den Timer. Mit der Taste ESCAPE wird der Roboter am Ende des Parcours angehalten. Dann setzt man ihn wieder an die Startposition (in der Simulation mit dem Befehl reset()) und drückt die Taste ENTER. Danach fährt der Roboter die Bahn ab und verwendet dabei die Informationen, die er in der Liste memory gespeichert hat.

 

#Ro12e.py

from simrobot import *
#from ev3robot import *
import time

RobotContext.useBackground("sprites/learntrack1.gif")
RobotContext.setStartPosition(385, 490)

def keepOnTrack():
    if vL < 500 and vR < 500:
        gear.forward()
    elif vL < 500 and vR > 500:
        gear.leftArc(0.1)
    elif vL > 500 and vR < 500:
        gear.rightArc(0.1)

robot = LegoRobot()
gear = Gear()
robot.addPart(gear)
ls1 = LightSensor(SensorPort.S1)#right
ls2 = LightSensor(SensorPort.S2)#left
robot.addPart(ls1)
robot.addPart(ls2)
gear.setSpeed(30)
memory = []
id = 0

# learning
startTime = time.time()

while not robot.isEscapeHit():
    vL = ls2.getValue()
    vR = ls1.getValue()
    keepOnTrack()
    if vL > 500 and vR > 500:  
        if time.time() - startTime > 2:
            gear.backward(100)   
            gear.left(900)
            memory.append(0)     
            gear.forward()           
        elif memory != []:
            memory.pop() 
            memory.append(1)
            gear.right(1800)
        print(memory)
        startTime = time.time()    
gear.stop()

# running
while not robot.isEscapeHit():
    while not robot.isEnterHit():
        pass
    robot.reset()
    id = 0
    while not robot.isEscapeHit():
        vR = ls1.getValue()
        vL = ls2.getValue()  
        keepOnTrack()
        if vL > 500 and vR > 500:  
            gear.stop()
            if len(memory) == id:
                break           
            if memory[id] == 0:
                gear.left(900)
            else:
                gear.right(900)            
            id += 1
    gear.stop()        
robot.exit()
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Erklärungen zum Programmcode:

print memory : die Liste memory wird im Ausgabefenster des TigerJython-Editors ausgegeben
while not robot.isEnterHit(): pass So lange nicht die Taste Enter gedrückt wurde, passiert nichts. Diese "Warte-Schleife" wird eingebaut, damit man den realen Roboter an die Ausgangsposition setzen kann.

 


Aufgaben: Serie 12

1)

Lerne den Roboter die nebenstehende Bahn durchzufahren, in dem du die Tasten LEFT, RIGHT, UP für vorwärts fahren und DOWN für stoppen verwendest.

Der Roboter speichert die Entscheiden links bzw. rechts und die Zeit, die er für die geraden Wege benötigt im Memory.

Für die Simulation kannst du das Hintergrundbild bg3.gif verwenden:

RobotContext.useBackground("sprites/bg3.gif")
RobotContext.setStartPosition(410, 475)
  

 

 

 

2)

Der Roboter soll mit Hilfe eines Colorsensors und eines Timers lernen die nebenstehende Bahn durchzufahren. Anschliessend soll er die Bahn selbständig (ohne die Wände zu berühren durchfahren.

Für die Simulation kannst du das Hintergrundbild colorparcours.png verwenden.

RobottContext.useBackground("sprites/colorparcours.png")
RobotContext.setStartPosition(35, 245)
RobotContext.setStartDirection(0)  

 

3)

Erstelle mit schwarzen Streifen und einer weissen Unterlage eine ähnliche Bahn wie im Beispiel 5. Dein EV3 mit zwei Lichtsensoren soll lernen diese Bahn abzufahren.

Im Simulationsmodus kannst du die Hintergrundbilder learntrack2.gif, learntrack3.gif und learntrack4.gif verwenden.

 

4)

Verwende im Simulationsmodus die Bahn learntrack4.gif. Der Roboter lernt mit Hilfe seiner beiden Lichtsensoren die Bahn vom Start zu Ziel abzufahren (siehe Beispiel 5). Vor dem Ziel wird er mit der Taste Esc angehalten. Danach rechnet er die gespeicherte Liste memory um, so dass er den Parcours vom Ziel zum Start abfahren kann. Mit Drücken der Taste Enter kehrt er um 180° um und fährt mit Hilfe der umgerechneten Liste zum Start zurück.

Anleitung: Mit dem Befehl memory.reverse() kannst du die Reihenfolge der Elemente in der Liste umkehren (das letzte Element wird das erste).

Teste dein Programm auch mit der Bahn learntrack2.gif.

 

5)

Der EV3-Roboter soll im Teachmode lernen, einen Kanal, welcher aus rechtwinklig stehenden Wegstücken besteht, durchzufahren. Löse die Aufgaben im Simulationsmodus mit dem Hintergrundbild "bg2.gif". Für die Steuerung im Teachmode verwendest du die Cursortasten wie folgt:

  • UP: startet die Vorwärtsbewegung
  • DOWN: stoppt die Vorwärtsbewegung
  • LEFT: dreht rechtwinklig nach links
  • RIGHT: dreht rechtwinklig nach rechts
  • ESCAPE: beendet die Lernphase
  • ENTER: startet die selbstständige Fahr durch den Kanal

Da der Roboter keine Sensoren hat, musst du die Vorwärtsbewegung jeweils mit dem Button UP starten und mit dem Button DOWN beenden und dabei die Zeit messen.

Anleitung: Es sinnvoll in der Liste memory für jede Teilstrecke 2 Werte zu speichern: die Zeit, die der Roboter zum Durchfahren braucht und 0 bzw. 1 für die darauf folgende Drehung. Dieses Zahlenpaar bezeichnest du als node und fügst es nach jeder Drehung zu der Liste hinzu.

dt = time.time() - startTime
t = (int)(dt * 1000) node = [t, 0]
memory.append(node)

In der running-Phase gehst du die Liste wie folgt durch:

for node in memory:
moveTime = node[0]
turn = node[1]