Robot Car steuern mit BlueDot

In der englischen Zeitschrift The MagPi, Ausgabe 114 habe ich ´mal wieder etwas für mich Neues und Interessantes zum Thema Roboter-Autos (robot car) gelesen. Schwerpunkt des Artikels war das relativ neue Zubehör Raspberry Pi Build HAT, eine Schnittstelle zu den Lego-Motoren. Also, wer einen Lego-Fuhrpark hat, kann nun seine Modelle mit einem Raspberry Pi programmieren und steuern.

Ein Aspekt der Steuerung ist aber auch für alle anderen Liebhaber von Roboter-Autos mit den viel verwendeten gelben Elektromotoren interessant: Die von Martin O’Hanlon – Mitarbeiter der Raspberry Pi Foundation - entwickelte Smartphone App BlueDot, eine Bluetooth Steuerung, die auf dem Raspberry Pi für eigene Zwecke angepasst werden kann. Das bedeutet, dass das Erscheinungsbild und die Funktionalität mit Python programmiert wird. Offensichtlich gibt es diese App nur für Android. Hier zunächst ein Screenshot von der App auf meinem Smartphone mit dem namensgebenden Blauen Punkt:

Verwendete Hardware:

1

Raspberry Pi mit Bluetooth, z.B. 3B+

1

Smart Robot Car Kit


Einen ausfühlichen Beitrag zum Smart Robot Car Kit finden Sie hier.

Wie gesagt, Erscheinungsbild, also Anzahl der Schaltflächen, Formen und Farben der Punkte sowie die Funktionen können nach eigenen Wünschen gestaltet werden. Später dazu mehr.

Auf meinem Android Smartphone habe ich im Play Store im Suchfenster Blue Dot eingegeben. Aus der Vielzahl der angezeigten Möglichkeiten haben mich der dicke blaue Punkt und die Unterzeile Martin O’Hanlon *Tools zur richtigen App geführt. Damit die geöffnete App später den Raspberry Pi findet, muss dieser zunächst am Smartphone unter Einstellungen/Bluetooth gekoppelt werden. Selbstmurmelnd muss auf beiden Geräten Bluetooth aktiviert sein und auf dem Raspberry Pi mit linkem Mausklick auf das Bluetooth-Symbol „Make Discoverable“ angewählt werden. 

Die nächsten beiden Bilder zeigen das grundsätzliche Koppeln von Smartphone und Raspberry Pi, sowie die App vor dem tatsächlichen Verbinden der App mit dem Raspi. Bei mehreren gekoppelten Geräten können hier auch mehrere Optionen angeboten werden. Einfach auf die zwei Zeilen mit raspberrypi und Adresse klicken, dann wird die Verbindung hergestellt, sofern auch das Programm auf dem Raspberry Pi gestartet wurde.

Auf dem Raspberry Pi muss die zugehörige Python-Bibliothek installiert werden. Das geschieht am besten mit dem Terminal-Befehl:

 sudo pip3 install bluedot

Im nächsten Schritt suchen wir mit Google im Browser „github bluedot“ und werden auf die Seite von Martin O’Hanlon geführt. Hier finden wir wichtige Infos, wie die Links zur „online documentation“ sowie vielen Programmbeispielen (Recipes). Ich lade kurzerhand alles als ZIP-Datei herunter, entpacke die Datei und verschiebe die Beispiele auf den Raspberry Pi, um alles auszuprobieren.

Hier empfehle ich Nachmachen, denn auf diese Weise lernt man die vielfältige Funktionalität der Programm-Bibliothek am schnellsten kennen. Denn, auch hier wiederhole ich mich, das Aussehen der App auf dem Smartphone und die Funktionalität werden auf dem Raspberry Pi programmiert.

Das nächste Bild vom Smartphone mit dem Joypad erhält man mit dem nachfolgenden Python-Code (Beispielprogramm joypad.py): 

joypad.py

 from bluedot import BlueDot
 from signal import pause
 def up():
     print("up")
 def down():
     print("down")
 def left():
     print("left")
 def right():
     print("right")
 def button_A():
     print("A")
 def button_B():
     print("B")
 bd = BlueDot(cols=5, rows=3)
 # dpad buttons
 bd.color = "gray"
 bd.square = True
 bd[0,0].visible = False
 bd[2,0].visible = False
 bd[0,2].visible = False
 bd[2,2].visible = False
 bd[1,1].visible = False
 bd[1,0].when_pressed = up
 bd[1,2].when_pressed = down
 bd[0,1].when_pressed = left
 bd[2,1].when_pressed = right
 # buttons
 bd[3,0].visible = False
 bd[4,0].visible = False
 bd[3,2].visible = False
 bd[4,2].visible = False
 bd[3,1].color = "blue"
 bd[3,1].square = False
 bd[3,1].when_pressed = button_A
 bd[4,1].color = "red"
 bd[4,1].when_pressed = button_B
 pause()

Mit bd = BlueDot(cols=5, rows=3)  wird das BlueDot-Objekt mit 5 Spalten und 3 Reihen instanziiert. Danach werden die unerwünschten Felder bd[0,0].visible = False  unsichtbar gemacht. Farbe und Form der Buttons werden z.B. mit bd.color = "gray" und bd.square = True  definiert. Mit Befehlen wie bd[1,0].when_pressed = up  werden selbst definierte Funktionen aufgerufen.

Im Hinblick auf mein Robot Car-Projekt, wird meine Aufmerksamkeit auf die Programme simple_robot.py und source_robot.py gelenkt. Bei beiden Programmen wird auf dem Smartphone nur der große blaue Punkt wie im ersten Bild angezeigt, aber die Funktionalität ist unterschiedlich.

simple_robot.py

 from bluedot import BlueDot
 from gpiozero import Robot
 from signal import pause
 bd = BlueDot()
 robot = Robot(left=(10, 9), right=(8, 7))
 def move(pos):
     if pos.top:
         robot.forward()
     elif pos.bottom:
         robot.backward()
     elif pos.left:
         robot.left()
     elif pos.right:
         robot.right()
 def stop():
     robot.stop()
 bd.when_pressed = move
 bd.when_moved = move
 bd.when_released = stop
 pause()

Für mein Robot Car mit Raspberry Pi und dem Motor Controller pHAT MotoZero von ThePiHut, muss ich nur die Zeile mit den GPIO-Pins des Objekts robot ändern. Ich benutze die Ausgänge für Motor 3 left=(23,16) und Motor 4 right=(13,18). Der zuvor in den Einstellungen gekoppelte Raspberry Pi wird nach dem Starten des Programms in der App angezeigt. Nach dem Antippen erscheint der blaue Punkt. Beim Tippen auf die vier „Himmelsrichtungen“ bewegen sich die Räder mit Vollgas, allerdings dreht bei mir das linke Rad in die falsche Richtung. Abhilfe wäre Vertauschen der GPIO-Nummern bei der Instanziierung des Objekts robot und Neustart des Programms, oder kurzerhand den Motor am Motor Controller umpolen.

Aber nur Vollgas vor oder zurück, oder high speed spinning auf der Stelle, sind unbefriedigend. Deshalb probiere ich als nächstes das Demo-Programm  source_robot.py.

 from gpiozero import Robot
 from bluedot import BlueDot
 from signal import pause
 def pos_to_values(x, y):
     left = y if x > 0 else y + x
     right = y if x < 0 else y - x
     return (clamped(left), clamped(right))
 def clamped(v):
     return max(-1, min(1, v))
 def drive():
     while True:
         if bd.is_pressed:
             x, y = bd.position.x, bd.position.y
             yield pos_to_values(x, y)
         else:
             yield (0, 0)
 if __name__ == '__main__':
     robot = Robot(left=(10, 9), right=(8, 7))
     bd = BlueDot()
     robot.source = drive()
     pause()

Damit kann das Robot Car auch langsamer fahren, weil dem blauen Punkt ein Koordinatensystem hinterlegt ist. Dabei liegen die Werte für x und y zwischen -1.0 und +1.0. Alternativ kann man auch Winkel und Abstand ermitteln. Ergebnis: Viel besser, aber die Kurvenfahrt ist immer noch unbefriedigend, denn das Programm kennt nur: ein Rad dreht vorwärts, das andere rückwärts, leider nicht: ein Rad dreht schneller als das andere für eine ruhige Bewegung.

An dieser Stelle fiel mir ein, dass wir im Frühjahr 2021 eine Blog-Reihe zum Thema Robot Car hatten, bei der wir auch Code für Kurvenfahrten entwickelt hatten. Damit ist dieser Blog-Beitrag sozusagen eine Fortsetzung des Blogs Robot Car mit Raspberry Pi vom 24. April 2021

Seinerzeit hatten wir einen Code entwickelt, mit jeweils 5 Fahrstufen je Richtung. Code 505 stand für Stillstand, die Anfangszahl 6 bis 10 für Vorwärtsfahrt, 4 bis 0 für Rückwärtsfahrt, die hintere Zahlengruppe für Links- bzw. Rechtskurve. Diesen Code konnten wir sowohl bei Tastatureingaben, als auch bei Funkübertragung mit HC-12 bzw. nRF24L01 benutzen. Der Vorteile der Fahrstufen liegt in der angepassten Pulsweitenmodulation für die Motoren. Für die unterste Fahrstufe benötigen die Motoren bereits 20% bis 40% der Spannung. Darunter brummen sie nur, drehen aber noch nicht. Und ggf. ist auch die Höchstspannung, z.B. von zwei Lithium-Batterien, zu viel für den Dauerbetrieb der Motoren, in meinem Fall nur 60% bis 70% des Höchstwertes.

Im nächsten Schritt möchte ich ein (Unter-)Programm entwickeln, das die x, y-Koordinaten des blauen Punkts erfasst und in die Indizes für meine Fahrstufenliste umrechnet. Als Ausgangspunkt wähle ich das Beispielprogramm dot_single_button_debug.py. Hierin werden alle wesentlichen Methoden (Funktionen) (im Folgenden fett gedruckt) exemplarisch angewendet:

dot_single_button_debug_erweitert.py

 from bluedot import BlueDot
 from time import sleep, time
 dot = BlueDot(auto_start_server = False)
 dot.allow_pairing()
 def pressed(pos):
     print("Pressed: x={} y={} angle={} distance={} middle={} top={} bottom={} left={} right={} time={}".format(pos.x, pos.y, pos.angle, pos.distance, pos.middle, pos.top, pos.bottom, pos.left, pos.right, time()))
 def released():
     print("Released: x={} y={}".format(dot.position.x, dot.position.y))
 def moved(pos):
     print("Moved: x={} y={}".format(pos.x, pos.y))
 def swiped(swipe):
     print("Swiped: up={} down={} left={} right={} speed={}".format(swipe.up, swipe.down, swipe.left, swipe.right, swipe.speed))
 def double_presed(pos):
     print("Double pressed: x={} y={}".format(pos.x, pos.y))
 def client_connected():
     print("connected callback")
 def client_disconnected():
     print("disconnected callback")
 dot.when_client_connects = client_connected
 dot.when_client_disconnects = client_disconnected
 dot.when_pressed = pressed
 dot.when_released = released
 dot.when_moved = moved
 dot.when_swiped = swiped
 dot.when_double_pressed = double_presed
 dot.start()
 dot.wait_for_press()
 print("wait for press")
 dot.wait_for_move()
 print("wait for move")
 dot.wait_for_release()
 print("wait for release")
 dot.wait_for_double_press()
 print("wait for double press")
 dot.wait_for_swipe()
 print("wait for swipe")
 try:
     while True:
         sleep(0.1)
 finally:
     dot.stop()

Wie bei der objektorientierten Programmierung häufig üblich, werden durch Methoden selbstdefinierte Funktionen aufgerufen. Erkannt werden dot.when_client_connects und dot.when_client_disconnects für die Verbindung Raspberry Pi und Smartphone, sowie dot.when_pressed, dot.when_released, dot.when_moved, dot.when_swiped und dot.when_double_pressed für die Interaktion mit dem blauen Punkt.

In den selbstdefinierten Funktionen werden mit dem print-Befehl jeweils nur die Art der Interaktion und die jeweiligen Daten ausgegeben. Hier setzte ich an, um jeweils aus den x- und y-Koordinaten meine Fahrstufen zu berechnen. Also: die Dezimalwerte zwischen -1.0 und +1.0 sollen in Integer-Zahlen zwischen 0 und 10 umgewandelt werden. Dazu multipliziere ich die Werte mit 5, runde ohne Nachkommastellen und addiere 5. Diese Werte sind dann die Indizes meiner Liste der Fahrstufen.

Nach reichlich Ausprobieren habe ich beschlossen, nur die Methode when_pressed zu verwenden. Das Robot Car fährt ungefähr in die Richtung, die auf dem blauen Punkt angetippt wird. Beim Antippen der Mitte, oder beim Unterbrechen der Bluetooth-Verbindung durch die Rücktaste (dreieckiges Icon) am Smartphone stoppt das Robot Car.

Als Nächstes muss ich diese Programm-Versatzstücke mit dem Programm aus dem Blog Robot Car mit Raspberry Pi vom 24. April 2021 verbinden. Im letzten Jahr hatte ich Tastaturbefehle für die Steuerung genommen. Dieser Teil kann nun entfallen, aber die Umsetzung der y- und x-Werte in Fahrtstufen möchte ich übernehmen.

robotCar_MotoZero3_4_Code505_bluedot.py (download)

 #! /usr/bin/python3
 # Code for driving a robot car with MotoZero
 # Motors attached to M3 and M4
 # For motor control, use Android App Bluedot by Martin O'Hanlon
 # Install Python library (module) with: sudo pip3 install bluedot
 # By Bernd54Albrecht for AZ-Delivery
 from bluedot import BlueDot
 from time import sleep
 from gpiozero import Robot, Motor, OutputDevice
 import _thread  
 dot = BlueDot(auto_start_server = False)
 dot.allow_pairing()
 robot = Robot(left=(23,16), right=(13,18))
 motor1_enable = OutputDevice(12, initial_value=1)
 motor2_enable = OutputDevice(25, initial_value=1)
 y = 5    # Index for rate of speed 0
 x = 5    # Index for straight ahead
 def pressed(pos):
     y = int(round(5*pos.y +5,0))
     x = int(round(5*pos.x +5,0))
     print("pressed, y, x = ",y,x)
     _thread.start_new_thread(motor,(y,x))
 def client_connected():
     print("connected callback")
 def client_disconnected():
     print("disconnected callback")
     _thread.start_new_thread(motor,(5,5))
 def getCode():
     dot.when_pressed = pressed
     dot.wait_for_press()
     print("wait for press")
     return
 def motor(y,x):
     print("y = ",y, " x = ", x)
     speedLevel = [-600,-500,-400,-320,-250,0,250,320,400,500,600]
     speed = speedLevel[y]
         if x==10:
         left = min(speed+250,600)
         right = max(speed-250,-600)
     elif x==9:
         left = min(speed+200,600)
         right = max(speed-200,-600)
     elif x==8:
         left = min(speed+150,600)
         right = max(speed-150,-600)    
     elif x==7:
         left = min(speed+100,600)
         right = max(speed-100,-600)
     elif x==6:
         left = min(speed+50,600)
         right = max(speed-50,-600)
     elif x==4:
         right = min(speed+50,600)
         left = max(speed-50,-600)
     elif x==3:
         right = min(speed+100,600)
         left = max(speed-100,-600)
     elif x==2:
         right = min(speed+150,600)
         left = max(speed-150,-600)          
     elif x==1:
         right = min(speed+200,600)
         left = max(speed-200,-600)
     elif x==0:
         right = min(speed+250,600)
         left = max(speed-250,-600)
     else:
         left = speed
         right = speed
     robot.value = (left/1000, right/1000)
 
 try:
     dot.start()
     dot.when_client_connects = client_connected
     dot.when_client_disconnects = client_disconnected  
     while True:
         getCode()
         sleep(0.1)
 finally:
     dot.stop()

Perfekt, die Fahrstufen und die Korrekturen für die Kurvenfahrten passen für die wenigen freien Quadratmeter in unserem Wohnzimmer genau richtig. Das muss bei einer anderen Spannungsversorgung nicht unbedingt der Fall sein. Dann bitte ggf. die Werte für die Fahrstufen und die Korrekturen für die Kurvenfahrten anpassen. Wichtig ist nur, dass der mittlere Wert der elf Fahrstufen (Index 5, weil die Zählung bei 0 beginnt!) immer 0 ist. Für Autostart des Programms und automatisches Herunterfahren des Raspi am Ende, verweise ich auf den Blog Robot Car mit Raspberry Pi vom 24. April 2021.

Zum Schluss zeige ich ein kleines Video meiner ersten Fahrversuche. Das Fahren ist nicht schwierig. Wenn man falsch getippt hat, einfach erneut drücken. Schwierig war für mich gleichzeitig Fahren und Filmen und dabei möglichst Smartphone und Roboterauto im Bild behalten. Nachmachen lohnt sich, macht echt Spaß. Danke an Martin O’Hanlon für diese leicht zu bedienende Bluetooth App.

 

Projekte für anfängerRaspberry pi

2 comments

Andreas Wolter

Andreas Wolter

@Sven: dieser Beitrag zeigt die Verwendung des Raspberry Pi auf dem Robot Car.

Grüße,
Andreas Wolter

Sven

Sven

Wie bekomme ich das Programm auf den esp32

Leave a comment

All comments are moderated before being published