Ein Lego Mindstorm Robot mit Raspberry Pi und BrickPi

Ich hab mir den Spaß erlaubt, unseren alten Lego Mindstorm NXT 2 aufzupimpen - mit einem Raspberry und einem auf Arduino Basis entwickelten Lego Controller von Dexter Industries - dem BrickPi.
Das Lego Mindstorm Konzept ist ja eigentlich prima, nur die Entwicklungsumgebung ist gruselig. Besonders vermisst hatte ich, dass man nicht in einer Hochsprache programmieren kann.

Glücklicherweise hat Lego das eingesehen und die neue Generation (EV3) auf Linux Basis aufgebaut. Glück für diejenigen, die sich einen neuen kaufen. Und für die anderen NXT2 Besitzer wie mich gibts eben das hübsche BrickPi Konzept.

Das BrickPi Konzept

Der BrickPi ist ein Arduino Board mit dem gleichen Formfaktor wie ein Raspberry; der BrickPi besitzt Anschlüsse für Lego Sensoren und Motoren. Man setzt den BrickPi Huckepack auf den Raspberry und verbindet so automatisch die IO Ports.

Der BrickPi läuft entkoppelt vom Raspberry, d.h. man kann nicht direkt über den Raspberry auf den BrickPi zugreifen: programmiert wird der Raspberry; Schnittstelle zum BrickPi sind die IO Ports.

Dexter liefert ein vorbereitetes Raspbian Image aus, dass für die Entwicklung vorbereitet ist. Entwickeln kann man in verschiedenen Sprachen; ich hab mich für Python entschieden. Der Einstieg ist denkbar leicht; man bekommt konkrete Baupläne und für Bots und Programme, die diese bedienen.

Mein Shooter Bot

Beim Herumexperimentieren mit den vorgegebenen Modell entstand die Idee, ein per Handy fernsteuerbares Fahrzeug zu entwickeln, welches ein Livebild überträgt und Kugeln abschiessen kann.

Die Vorlage hierfür ist der BrowserBot von Dexter (GitHub: ) Das zugehörige Python-Script ist simpel aufgebaut. Ein tornado Websocket-Server nimmt die Befehle aus einer Webanwendung entgegen und setzt diese in Befehle für den BrickPi um. Dieser wiederum steuert damit  die Motoren. Der tornado Server liefert zudem auch die HTML Seite mit den Steuercontrols und der Videoanzeige aus.

Videostreaming mit Raspberry

Die Webanwendung beinhaltet ein Video, das vom Robot in die Website live gestreamt wird. Aufgrund der schwächlichen Hardware war es mir nicht möglich, einen Videostream per raspvid zu erzeugen und sauber zu übertragen; Selbst bei kleinen Videogrößen (320x240) und 5 fps brach mit das System zusammen.

Die Lösung hierfür lautet mpeg_streamer. Man erzeugt per raspstill images; und mpeg_streamer erzeugt einen Server, über den ein mp4-Stream mit den Bildern abgerufen werden kann. Das funktioniert zuverlässig und hat keine Systeminstabilitäten hervorgerufen:

    !/bin/bash
    
    raspistill --nopreview -w 320 -h 240 -q 5 -o /home/pi/bot/stream/pic.jpg -tl 100 -t 9999999 -th 0:0:0 &
    LD_LIBRARY_PATH=/usr/local/lib mjpg_streamer -i "input_file.so -f /home/pi/bot/stream -n pic.jpg" -o "output_http.so -w /usr/local/www"

Das Python Script - der shooterBot.py

Das Ganze ist ziemlich straightforward implementiert. Die Schnittstelle zwischen Raspberry und BrickPi sind die IO Ports des Raspberry, der Brickpi sitzt ja physikalisch genau auf diesen Ports und setzt die Befehle für Lego um. Die Lego Motoren werden wie bei Lego Mindstorms üblich mit RJ45-Kabeln verbunden. Gesteuert wird der Bot über eine Website; diese fängt Touchevents auf den Steuertasten ab, liefert diese per Websockets dem Python-Tornado, der wiederum steuert den BrickPi.

    
    ...
    from BrickPi import *   #import BrickPi.py file to use BrickPi operations
    
    # tornado import
    import threading
    import tornado.ioloop
    import tornado.web
    import tornado.websocket
    import tornado.template
    
    leftWheel  = PORT_B
    rightWheel = PORT_C
    shooter    = PORT_D
    
    speed = -200
    
    ...
    
    class WSHandler(tornado.websocket.WebSocketHandler):
            def open(self):
                    print 'connection opened...'
            def on_message(self, message):
                    global c
                    print 'received:', message
                    if message == "u":
                      print "Running Forward"
                      BrickPi.MotorSpeed[leftWheel] = speed
                      BrickPi.MotorSpeed[rightWheel] = speed
    ...
                    if message == "y":
                      print "Shoot"
                      motorRotateDegree([255],[360],[shooter])
                    BrickPiUpdateValues();
            def on_close(self):
                    print 'connection closed...'
    
    application = tornado.web.Application([
      (r'/ws', WSHandler),
      (r'/', MainHandler),
      (r"/(.*)", tornado.web.StaticFileHandler, {"path": "./"}),
    ])

Die Webseite mit der Steuerung

Die Webseite hat als Besonderheit die Verwendung von Websockets:

    
    ...
    <script type="mce-text/javascript" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
    <script type="mce-text/javascript">
    $( document ).ready(function() {
    
          $(".touch").bind('touchend', function() {
              if ( 'b' != commands[this.id] ) {
                cmd = 'b'
                sendCmd();
              }
          });
        $(".touch").bind('touchstart', function() {
              cmd = commands[this.id];
              sendCmd();
          });
    });
    
    function sendCmd()
    {
        var host =  "ws://"+document.location.host+":80/ws";
        var socket;
    
        try {
            socket = new WebSocket(host);
            if(socket) {
                socket.onopen = function() {
                    buttons();    // function for detecting the button press on webpage
                }
                function buttons() {
                    socket.send(cmd);
                }
    ...
    }
    
    ...
    </script>
    </head>
    <body>
    <div class="touch" style="left: 140px; top: 10px" id="Fwd" ></div>
    <div class="touch" style="left: 10px; top: 115px" id="Left" ></div>
    <div class="touch" style="left: 280px; top: 115px" id="Right" ></div>
    <div class="touch" style="left: 140px; top: 115px" id="Shoot" ></div>
    <div class="touch" style="left: 140px; top: 200px" id="Back" ></div>
    <div class="touch" style="left: 140px; top: 250px" id="Stop" >Stop</div>
    
    <iframe src="http://192.168.12.201:8080?action=stream" width="320" height="240" name="video">
    </iframe>
    
    

Powering - Akku und 9V

Um die BrickPi Roboter in Betrieb zu setzen, benötigt man entweder ein Battery Pack von Dexter (hab ich nicht), oder eine separate Speisung von BrickPi und Raspberry. Der Raspberry wird von einem Akkupack betrieben; der BrickPi hat keine 9V Batterie. Die Motoren saugen die Batterie schnell leer; stundenlangen Spaß darf man nicht erwarten.

Die Lego Hardware

Es hat ein bisschen gedauert, bis ich die richtige Konstruktion zusammen hatte. Die Bewegung des Gefährts erfolgt über zwei Motoren/Räder, die entweder beide in eine Richtung drehen (vor- und zurück), oder man steuert nur einen Motor an (Links- und Rechtsdrehung. Das andere Räderpaar hat keinen Antrieb. Auf dem Grundchassis sitzt die Raspberry/BrickPi Steuereinheit; auf dieser dann die Schussvorrichtung. Das Fahrzeug bewegt sich auf glatten Flächen ganz gut; auf Teppich ist der Widerstand dann doch schnell zu groß.

Die Schussvorrichtung kann man sich aus bestehenden Bauplänen abschauen.

Die Kamera hab ich nur mit kleinen Modifikationen befestigen können; die Bohrungen der Kamerahalterung passen nicht ganz auf die Lego-Maße.

Viel Spaß beim Nachbauen :-)

150214-DSCF0121