Raspi, Motoren per Tastatur/Controller ansteuern

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

Hallo zusammen!

eine Grundsatzfrage:
ich möchte schlussendlich an einen Rpi mehrere Motoren ansteuern (ferngesteuerter Radlader von Lego). Die Ansteuerung erfolgt per Bluetooth durch Tastatur oder PS3controller.
Da ich noch recht neu bin möchte ich wissen, welchen Weg ihr einschlagen würdet. Input, curses oder vllt. was anderes?
BlackJack

@gamble: Tastatur könnte man mit `curses` lösen, aber so ein PS3-Controller wird ja wahrscheinlich aus Sicht des Systems keine Tastatur sondern ein Joystick sein. Da müsste man sich dann eine separate Bibliothek für suchen, oder man nimmt so etwas wie Pygame, was Tastatur und Joysticks abfragen kann.
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

ja dankeschön... jetzt weiss ich mal die Richtung

weiß jemand wo ich sixaxis und sixad her bekomme? sudu apt-get install geht bei beiden nicht...
BlackJack

@gamble: Vielleicht haben die ja eine Homepage. Ich nehme mal an das es Python-Module sind?
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

nein sind keine Python module. Sind kleine Programme welche die Anbindung des Controllers ermöglichen.
Mittlerweile habe ich aber gelesen, dass raspbian nicht unbedingt das beste Betriebssystem ist um mein Projekt umzusetzen und so werde ich auf ubuntu wechseln. DOrt müssten die beiden auch zu finden sein
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

so nun habe ich erste Erfolge und Zeilen geschrieben und mein Radlader fährt auch schon so wie ichs will. Haben die Profis noch ein paar Tipps, was besser oder "sauberer" geschrieben werden kann?

Code: Alles auswählen

#!/usr/bin/env python
import RPi.GPIO as GPIO
import time
import sys
import pygame
GPIO.setwarnings (False)

# setup
Dsp = 10
Mfw = 11
Mbw = 12

GPIO.setmode(GPIO.BOARD)
GPIO.setup(Dsp, GPIO.OUT)    # drive speed
GPIO.setup(Mfw, GPIO.OUT)    # drive forward
GPIO.setup(Mbw, GPIO.OUT)    # drive backword
GPIO.output(Mfw, GPIO.LOW)
GPIO.output(Mbw, GPIO.LOW)
Fsp = GPIO.PWM(Dsp, 1000)
Fsp.start(0)


pygame.init()
pygame.display.set_mode((200,200))
pygame.display.set_caption("Lader")

#variables
timedrive = 0.2		# time between speed steps
drivemin = 20       # minimal driving speed
drivemax = 100      # maximal driving speed
drivestep = 5		# steps driving speed
setfw = 0			# status forward
setbw = 0			# status backward
fw = 0
bw = 0


def loop(fw, bw, setfw, setbw, Fsp):
	while True:
		for event in pygame.event.get():
			if event.type == pygame.KEYDOWN:
				if event.key == pygame.K_ESCAPE:
					destroy()
				if event.key == pygame.K_UP:
					fw = 1
				if event.key == pygame.K_DOWN:
					bw = 1
			if event.type == pygame.KEYUP:
				if event.key == pygame.K_UP:
					fw = 0
				if event.key == pygame.K_DOWN:
					bw = 0
										
		if fw == 0 and bw == 0 and setfw == 0 and setbw == 0:	#nothing is going on
			time.sleep(timedrive)
		
		elif (fw == 1):	#drive forward
			if (setbw == 0):
				GPIO.output (Mfw, True)
				if setfw < drivemin:
					setfw = drivemin
					Fsp.ChangeDutyCycle(setfw)
					print (setfw)
					time.sleep(timedrive)
				else:
					if (setfw + drivestep) < drivemax:
						setfw = setfw + drivestep
						Fsp.ChangeDutyCycle(setfw)
						print (setfw)
						time.sleep(timedrive)
					else:
						setfw = drivemax
						time.sleep(timedrive)
			else:
				if setbw > drivemin:
					setbw = setbw - drivestep
					Fsp.ChangeDutyCycle(setbw)
					print (setbw)
					time.sleep(timedrive)
				else:
					setbw = 0
					GPIO.output (Mbw, False)
					Fsp.ChangeDutyCycle(setbw)
					print (setbw)
					time.sleep(timedrive)
					
		elif (bw == 1):	#drive backward
			if (setfw == 0):
				GPIO.output (Mbw, True)
				if setbw < drivemin:
					setbw = drivemin
					Fsp.ChangeDutyCycle(setbw)
					print (setbw)
					time.sleep(timedrive)
				else:
					if (setbw + drivestep) < drivemax:
						setbw = setbw + drivestep
						Fsp.ChangeDutyCycle(setbw)
						print (setbw)
						time.sleep(timedrive)
					else:
						setbw = drivemax
						time.sleep(timedrive)
			else:
				if setfw > drivemin:
					setfw = setfw - drivestep
					Fsp.ChangeDutyCycle(setfw)
					print (setfw)
					time.sleep(timedrive)
				else:
					setfw = 0
					GPIO.output (Mfw, False)
					Fsp.ChangeDutyCycle(setfw)
					print (setfw)
					time.sleep(timedrive)
		
		elif (setfw > 0):
			if setfw > drivemin:
				setfw = setfw - drivestep
				Fsp.ChangeDutyCycle(setfw)
				print (setfw)
				time.sleep(timedrive)
			else:
				setfw = 0
				GPIO.output (Mfw, False)
				Fsp.ChangeDutyCycle(setfw)
				print (setfw)
				time.sleep(timedrive)
		elif (setbw > 0):
			if setbw > drivemin:
				setbw = setbw - drivestep
				Fsp.ChangeDutyCycle(setbw)
				print (setbw)
				time.sleep(timedrive)
			else:
				setbw = 0
				GPIO.output (Mbw, False)
				Fsp.ChangeDutyCycle(setbw)
				print (setbw)
				time.sleep(timedrive)
		else:
			print ("Fehler!!!!")


def destroy():
	GPIO.cleanup()     
	sys.exit()

try:
	loop(fw, bw, setfw, setbw, Fsp)
except KeyboardInterrupt:  # When 'Ctrl+C' is pressed, the child program destroy() will be  executed.
	destroy()
Das sind ca. 20% vom Programm. wie wird das ev. mit der Auslastung sein?
BlackJack

@gamble: Das was Du da als „variables“ im Kommentar bezeichnet hast sind keine Variablen. Es sind Konstanten. Einige davon eher nutzlose Konstanten, weil die nicht wirklich gebraucht werden ausser um als Argumente an `loop()` übergeben zu werden. Variablen haben auf Modulebene auch nichts zu suchen. Da gehören nur Definitionen von Konstanten, Funktionen, und Klassen hin. Eine Funktion sollte in sich geschlossen sein und nur Werte (ausser Konstanten) verwenden die als Argumente übergeben wurden. Sonst werden Programme sehr schnell unübersichtlich. Auch der Initialisierungscode von GPIO und Pygame sollte in einer Funktion stehen. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Konstanten werden per Konvention komplett in Grossbuchstaben geschrieben. Andere Namen, ausser Klassennamen, haben keine Grossbuchstaben. Siehe auch den Style Guide for Python Code. Da steht auch das vier Leerzeichen pro Ebene zum Einrücken verwendet werden.

Die Namen sind teilweise sehr schlecht gewählt. Namen sollen dem Leser vermitteln was der Wert bedeutet und nicht aus kryptischen Kürzeln bestehen bei denen man erst einmal rätseln muss was die bedeuten. Wenn man ein Kürzel hat und einen Kommentar mit dem *eigentlichen* Namen dran schreibt, dann sollte der Namen besser sein, und der Kommentar wird dann überflüssig.

Das erste ``if`` in der Ereignisschleife sollte eher als ``else`` am Ende stehen. Dann ist es einfacher diese Schleife zu erweitern statt das man die Bedingung im ersten ``if`` um alles Mögliche erweitern muss. Die Logik dort erscheint mir auch etwas sehr kompliziert formuliert zu sein. Der Code sieht in den einzelnen Zweigen sehr ähnlich aus, als wenn da grosse Teile kopiert und leicht angepasst wurden. Das ist gar nicht gut. Ausserdem scheint *jeder* Zweig mit dem gleichen `sleep()` zu enden. Das gehört dann nicht in *jeden* Zweig sondern *einmal* ans Ende. Wobei das in einer Ereignisschleife vielleicht auch gar nichts zu suchen hat. Das hält nur auf. Man würde da eher ein Timer-Ereignis mit in die Schleife einbauen.

Um Bedingungen gehören keine unnötigen Klammern.

Wenn man Wahrheitswerte meint, sollte man nicht 0 und 1 verwenden. Und dann kann man auch gleich das vergleichen mit literalen Werten weglassen. Aus ``if fw == 1:`` wird dann ``if fw:`` und aus ``if fw == 0:`` wird ``if not fw:``.

Die `destroy()`-Funktion ist nicht schön. Man sollte `sys.exit()` vermeiden wenn man das auch durch den normalen Programmfluss ausdrücken kann. Also zum Beispiel bei der Escape-Taste einfach die Ereignisschleife verlassen. Und Aufräumarbeiten in den ``finally``-Zweig von einem ``try``/``finally`` stecken.

Was man statt so vieler einzelner Flags und Werte eigentlich nur braucht ist ein Flag für die Richtung und eine Zahl für die Geschwindigkeit. Wenn man will, kann man das sogar auf eine Zahl für die Geschwindigkeit reduzieren und das Vorzeichen für die Richtung verwenden. Wenn man da dann noch vernünftige Namen verwendet, wird das alles kürzer und verständlicher.

Und danach könnte man dann Anfangen das ganze objektorientiert zu schreiben und zum Beispiel den Motor in einen eigenen Datentyp kapseln.
BlackJack

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
import time
import sys

import pygame
from RPi import GPIO

SPEED_PIN = 10
FORWARD_PIN = 11
BACKWARD_PIN = 12


SPEED_CHANGE_INTERVAL = 0.2
MIN_SPEED = 20
MAX_SPEED = 100
SPEED_STEP = 5

BACKWARD, STOP, FORWARD = -1, 0, 1


def run(speed_pwm):
    speed = 0
    requested_direction = None
    while True:
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    return
                elif event.key == pygame.K_UP:
                    requested_direction = FORWARD
                elif event.key == pygame.K_DOWN:
                    requested_direction = BACKWARD
            elif event.type == pygame.KEYUP:
                if event.key in [pygame.K_UP, pygame.K_DOWN]:
                    requested_direction = None

        if requested_direction == FORWARD:
            speed_delta = SPEED_STEP
        elif requested_direction == BACKWARD:
            speed_delta = -SPEED_STEP
        elif requested_direction is None:
            speed_delta = 0
        else:
            assert False

        speed = max(min(speed + speed_delta, MAX_SPEED), -MAX_SPEED)
        if abs(speed) < MIN_SPEED:
            speed = MIN_SPEED * (-1 if speed < 0 else 1)

        GPIO.output(FORWARD_PIN, speed > 0)
        GPIO.output(BACKWARD_PIN, speed < 0)
        speed_pwm.ChangeDutyCycle(abs(speed))

        time.sleep(SPEED_CHANGE_INTERVAL)


def main():
    GPIO.setwarnings(False)
    try:
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(
            [SPEED_PIN, FORWARD_PIN, BACKWARD_PIN], GPIO.OUT, initial=GPIO.LOW
        )
        speed_pwm = GPIO.PWM(SPEED_PIN, 1000)
        speed_pwm.start(0)

        pygame.init()
        try:
            pygame.display.set_mode((200, 200))
            pygame.display.set_caption('Lader')
            run(speed_pwm)
        finally:
            pygame.quit()
    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()


if __name__ == '__main__':
    main()
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

Vielen Dank!
Hilft mir sehr viel weiter. Jetzt seh ich wie ich dort hin komme, wo ich hin möchte.
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

so ich habe noch ein "Sanftes Anfahren-" und "stehen bleiben" eingefügt. ist das soweit vertretbar?

Code: Alles auswählen

#!/usr/bin/env python
import time
import sys
 
import pygame
from RPi import GPIO
 
SPEED_PIN = 10
FORWARD_PIN = 11
BACKWARD_PIN = 12
 
 
SPEED_CHANGE_INTERVAL = 0.2
MIN_SPEED = 50
MAX_SPEED = 100
SPEED_STEP_UP = 5
SPEED_STEP_DOWN = 10
 
BACKWARD, STOP, FORWARD = -1, 0, 1
 
 
def run(speed_pwm):
    speed = 0
    requested_direction = None
    while True:
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    return
                elif event.key == pygame.K_UP:
                    requested_direction = FORWARD
                elif event.key == pygame.K_DOWN:
                    requested_direction = BACKWARD
            elif event.type == pygame.KEYUP:
                if event.key in [pygame.K_UP, pygame.K_DOWN]:
                    requested_direction = None
 
        if requested_direction != None:
            if speed * requested_direction < MIN_SPEED:
                speed = MIN_SPEED * requested_direction
            elif speed * requested_direction < MAX_SPEED:
                speed += SPEED_STEP_UP * requested_direction

        else:
            if speed > 0:
                if speed > MIN_SPEED:
                    speed -= SPEED_STEP_DOWN
                else:
                    speed = 0
            elif speed < 0:
                if abs(speed) > MIN_SPEED:
                    speed += SPEED_STEP_DOWN
                else:
                    speed = 0
 
        GPIO.output(FORWARD_PIN, speed > 0)
        GPIO.output(BACKWARD_PIN, speed < 0)
        speed_pwm.ChangeDutyCycle(abs(speed))
 
        time.sleep(SPEED_CHANGE_INTERVAL)
 
 
def main():
    GPIO.setwarnings(False)
    try:
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(
            [SPEED_PIN, FORWARD_PIN, BACKWARD_PIN], GPIO.OUT, initial=GPIO.LOW
        )
        speed_pwm = GPIO.PWM(SPEED_PIN, 1000)
        speed_pwm.start(0)
 
        pygame.init()
        try:
            pygame.display.set_mode((200, 200))
            pygame.display.set_caption('Lader')
            run(speed_pwm)
        finally:
            pygame.quit()
    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()
 
 
if __name__ == '__main__':
    main()
Zuletzt geändert von Anonymous am Freitag 27. Mai 2016, 21:20, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@gamble: warum definierst Du Dir eine Konstante STOP, wenn Du für Stop None verwendest?
Das Abbremsen kann man kompakter schreiben als:

Code: Alles auswählen

speed = (-1 if speed < 0 else 1) * max(0, abs(speed) - SPEED_STEP_DOWN)
BlackJack

@Sirius3: Die Konstanten sind ja von mir übernommen und `requested_direction` auf STOP zu setzen hat eine andere Aussage (Ich will anhalten) als None (Ich will nichts ändern). `STOP` ist als Konstante überflüssig und noch ein Überbleibsel als ich übergangsweise noch Richtung und Geschwindigkeit getrennt modelliert hatte statt das Vorzeichen der Geschwindigkeit für die Richtung zu verwenden.
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

@ Sirius3 - mein Abbremsen darf nur bis ur MIN-Grenze gehen, da der Motor darunter nicht mehr genug Drehmoment hat...

So der Radlader funktioniert nun vollständig. Auch selbstständiges "stehenbleiben" und "zurücklenken" funktioniert bestens.
seit ihr soweit zufrieden oder was muss noch verbessert werden?

Code: Alles auswählen

#!/usr/bin/env python
import time
import sys
 
import pygame
from RPi import GPIO
 
SPEED_PIN = 10
FORWARD_PIN = 11
BACKWARD_PIN = 12
STEERING_PIN = 13
RIGHT_PIN = 15
LEFT_PIN = 16
UP_PIN = 35
DOWN_PIN = 36
OPEN_PIN = 37
CLOSE_PIN = 38
READY_PIN = 40
 
CHANGE_INTERVAL = 0.2
MIN_SPEED = 50
MAX_SPEED = 100
SPEED_STEP_UP = 5
SPEED_STEP_DOWN = 10
 
BACKWARD, FORWARD = -1, 1
LEFT, RIGHT = 1, -1
pos = [0.0, 26.4, 37.9, 49.4, 63.2, 74.7, 87.4, 100.0]

def run(speed_pwm, right_pwm, left_pwm):
    speed = 0
    steering = 0
    step_steering = 0
    requested_direction = None
    requested_steering = None
    while True:
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    return
                elif event.key == pygame.K_UP:
                    requested_direction = FORWARD
                elif event.key == pygame.K_DOWN:
                    requested_direction = BACKWARD
                elif event.key == pygame.K_LEFT:
                    requested_steering = LEFT
                elif event.key == pygame.K_RIGHT:
                    requested_steering = RIGHT
                elif event.key == pygame.K_w:
                    GPIO.output(UP_PIN, GPIO.HIGH)
                elif event.key == pygame.K_s:
                    GPIO.output(DOWN_PIN, GPIO.HIGH)
                elif event.key == pygame.K_a:
                    GPIO.output(OPEN_PIN, GPIO.HIGH)
                elif event.key == pygame.K_d:
                    GPIO.output(CLOSE_PIN, GPIO.HIGH)
            elif event.type == pygame.KEYUP:
                if event.key in [pygame.K_UP, pygame.K_DOWN]:
                    requested_direction = None
                elif event.key in [pygame.K_LEFT, pygame.K_RIGHT]:
                    requested_steering = None
                elif event.key == pygame.K_w:
                    GPIO.output(UP_PIN, GPIO.LOW)
                elif event.key == pygame.K_s:
                    GPIO.output(DOWN_PIN, GPIO.LOW)
                elif event.key == pygame.K_a:
                    GPIO.output(OPEN_PIN, GPIO.LOW)
                elif event.key == pygame.K_d:
                    GPIO.output(CLOSE_PIN, GPIO.LOW)
 
        if requested_direction != None:
            if speed * requested_direction < MIN_SPEED:
                speed = MIN_SPEED * requested_direction
            elif speed * requested_direction < MAX_SPEED:
                speed += SPEED_STEP_UP * requested_direction

        else:
            if speed > 0:
                if speed > MIN_SPEED:
                    speed -= SPEED_STEP_DOWN
                else:
                    speed = 0
            elif speed < 0:
                if abs(speed) > MIN_SPEED:
                    speed += SPEED_STEP_DOWN
                else:
                    speed = 0
            else:
                right_pwm.ChangeDutyCycle(0)
                left_pwm.ChangeDutyCycle(0)
        



        steering = pos[abs(step_steering)]
        if step_steering > 0:
            right_pwm.ChangeDutyCycle(abs(steering))
        elif step_steering < 0:
            left_pwm.ChangeDutyCycle(abs(steering))

        if requested_steering != None:
            if abs(step_steering) < 7:
                step_steering += requested_steering

        else:
            if step_steering < 0:
                step_steering += 1
            elif step_steering > 0:
                step_steering -= 1
 
        GPIO.output(FORWARD_PIN, speed > 0)
        GPIO.output(BACKWARD_PIN, speed < 0)
        speed_pwm.ChangeDutyCycle(abs(speed))
        time.sleep(CHANGE_INTERVAL)
 
 
def main():
    GPIO.setwarnings(False)
    try:
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(
            [SPEED_PIN, FORWARD_PIN, BACKWARD_PIN, STEERING_PIN, RIGHT_PIN, LEFT_PIN, 
            UP_PIN, DOWN_PIN, OPEN_PIN, CLOSE_PIN, READY_PIN], GPIO.OUT, initial=GPIO.LOW
            )
        GPIO.output(READY_PIN, GPIO.HIGH)
        GPIO.output(STEERING_PIN, GPIO.HIGH)
        speed_pwm = GPIO.PWM(SPEED_PIN, 1000)
        speed_pwm.start(0)
        right_pwm = GPIO.PWM(RIGHT_PIN, 1150)
        right_pwm.start(0)
        left_pwm = GPIO.PWM(LEFT_PIN, 1150)
        left_pwm.start(0)
 
        pygame.init()
        try:
            pygame.display.set_mode((200, 200))
            pygame.display.set_caption('Lader')
            run(speed_pwm, right_pwm, left_pwm)
        finally:
            pygame.quit()
    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()
 
 
if __name__ == '__main__':
    main()
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

so kleines update...

mein Radlader funktioniert nun auch mit Ps3 Controller und Tastatur gleichzeitig. Ist zwar etwas umständlich, aber was macht man nicht alles um ans Ziel zu kommen :K

Info am Rade:
Legomotoren werden über Motorcontroller angesteuert. Die Lenkung ist allerdings direkt über RIGHT_PIN und LEFT_PIN gesteuert und bekommt per STEERING_Pin nur die Versorgungsspannung...

Code: Alles auswählen

#!/usr/bin/python
import time
import sys
import os
 
import pygame
from RPi import GPIO
 
SPEED_PIN = 10
FORWARD_PIN = 11
BACKWARD_PIN = 12
STEERING_PIN = 13
RIGHT_PIN = 15
LEFT_PIN = 16
UP_PIN = 35
DOWN_PIN = 36
OPEN_PIN = 37
CLOSE_PIN = 38
READY_PIN = 40
 
CHANGE_INTERVAL = 0.1
MIN_SPEED = 55
MAX_SPEED = 100
SPEED_STEP_UP = 3
SPEED_STEP_DOWN = 5
BACKWARD, FORWARD = -1, 1
LEFT, RIGHT = -1, 1
JOYSTICK, KEYBOARD = 2, 1
pos = [0.0, 26.4, 37.9, 49.4, 63.2, 74.7, 87.4, 100.0]


def run(speed_pwm, right_pwm, left_pwm, controller):
    speed = 0
    speed_from_keyboard = 0
    speed_from_controller = 0
    steering = 0
    steeringleft = 0
    steeringright = 0
    steering_keyboard = 0
    steering_controller = 0
    step_steering = 0
    axis_steering = 0
    axis_direction = 0
    keyboard_direction = None
    keyboard_side = None
    requested_input = JOYSTICK

    while True:
    	if controller.get_button(14) or pygame.key.get_pressed()[pygame.K_ESCAPE]: # PS Taste
            return
        if controller.get_button(3): # Start Taste
            requested_input = JOYSTICK
        if controller.get_button(0): # Select Taste
            requested_input = KEYBOARD
        if controller.get_axis(0) > 0.3 or pygame.key.get_pressed()[pygame.K_d]:
            GPIO.output(OPEN_PIN, GPIO.HIGH)
        else:
            GPIO.output(OPEN_PIN, GPIO.LOW)
        
        if controller.get_axis(0) < -0.3 or pygame.key.get_pressed()[pygame.K_a]:
            GPIO.output(CLOSE_PIN, GPIO.HIGH)
        else:
            GPIO.output(CLOSE_PIN, GPIO.LOW)
        
        if controller.get_axis(1) > 0.3 or pygame.key.get_pressed()[pygame.K_s]:
            GPIO.output(DOWN_PIN, GPIO.HIGH)
        else:
            GPIO.output(DOWN_PIN, GPIO.LOW)
        
        if controller.get_axis(1) < -0.3 or pygame.key.get_pressed()[pygame.K_w]:
            GPIO.output(UP_PIN, GPIO.HIGH)
        else:
            GPIO.output(UP_PIN, GPIO.LOW)
    	
    	for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    keyboard_direction = FORWARD
                elif event.key == pygame.K_DOWN:
                    keyboard_direction = BACKWARD
                elif event.key == pygame.K_LEFT:
                    keyboard_side = LEFT
                elif event.key == pygame.K_RIGHT:
                    keyboard_side = RIGHT
            elif event.type == pygame.KEYUP:
                if event.key in [pygame.K_UP, pygame.K_DOWN]:
                    keyboard_direction = None
                elif event.key in [pygame.K_LEFT, pygame.K_RIGHT]:
                    keyboard_side = None

#   Keyboard speed                    
        if keyboard_direction != None:
            if speed_from_keyboard * keyboard_direction < MIN_SPEED:
                speed_from_keyboard = MIN_SPEED * keyboard_direction
            elif speed_from_keyboard * keyboard_direction < MAX_SPEED:
                speed_from_keyboard += SPEED_STEP_UP * keyboard_direction
        else:
            if speed_from_keyboard > 0:
                if speed_from_keyboard > MIN_SPEED:
                    speed_from_keyboard -= SPEED_STEP_DOWN
                else:
                    speed_from_keyboard = 0
            elif speed_from_keyboard < 0:
                if abs(speed_from_keyboard) > MIN_SPEED:
                    speed_from_keyboard += SPEED_STEP_DOWN
                else:
                    speed_from_keyboard = 0
            else:
                speed_from_keyboard = 0


#   Keyboard steering
        if keyboard_side != None:
            if abs(step_steering) < 7:
                step_steering += keyboard_side
        else:
            if step_steering < 0:
                step_steering += 1
            elif step_steering > 0:
                step_steering -= 1
        
        if step_steering < 0:
            steering_keyboard = -1 * pos[abs(step_steering)]
        else:
            steering_keyboard = pos[abs(step_steering)]

#   Controller speed
        if (controller.get_axis(3) * -1.0) > 0.3:
            speed_from_controller = round(100.0 - ((1.0 - (controller.get_axis(3) * -1.0)) * 64.3), 1)
        elif controller.get_axis(3) > 0.3:
            speed_from_controller = (round(100.0 - ((1.0 - controller.get_axis(3)) * 64.3), 1)) * -1
        else:
            speed_from_controller = 0

#   Controller steering
        axis_steering = controller.get_axis(2) * 100
        if controller.get_axis(2) < 0:
            axis_direction = -1
        else:
            axis_direction = 1

        if abs(axis_steering) > 94:
            steering_controller = pos[7] * axis_direction
        elif abs(axis_steering) > 81:
            steering_controller = pos[6] * axis_direction
        elif abs(axis_steering) > 69:
            steering_controller = pos[5] * axis_direction
        elif abs(axis_steering) > 57:
            steering_controller = pos[4] * axis_direction
        elif abs(axis_steering) > 45:
            steering_controller = pos[3] * axis_direction
        elif abs(axis_steering) > 32:
            steering_controller = pos[2] * axis_direction
        elif abs(axis_steering) > 20:
            steering_controller = pos[1] * axis_direction
        else:
            steering_controller = 0

#   Which input?
        if requested_input == KEYBOARD:
            speed = speed_from_keyboard
            steering = steering_keyboard
        else:
            speed = speed_from_controller
            steering = steering_controller

#   steering output
        if steering < 0:
            steeringleft = 0
            steeringright = abs(steering)
        elif steering > 0:
            steeringright = 0
            steeringleft = abs(steering)
        else:
            steeringleft = 0
            steeringright = 0


        right_pwm.ChangeDutyCycle(steeringright)
        left_pwm.ChangeDutyCycle(steeringleft)
 

        GPIO.output(FORWARD_PIN, speed > 0)
        GPIO.output(BACKWARD_PIN, speed < 0)
        speed_pwm.ChangeDutyCycle(abs(speed))
        time.sleep(CHANGE_INTERVAL)

def main():
    GPIO.setwarnings(False)
    printed = 0
    try:
        while not os.path.exists("/dev/input/js0"):
            if printed:
                time.sleep(0.5)
            else:
                print ("waiting for controller")
                printed = 1
    	print ("controller connected")
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(
            [SPEED_PIN, FORWARD_PIN, BACKWARD_PIN, STEERING_PIN, RIGHT_PIN, LEFT_PIN, 
            UP_PIN, DOWN_PIN, OPEN_PIN, CLOSE_PIN, READY_PIN], GPIO.OUT, initial=GPIO.LOW
            )
        GPIO.output(READY_PIN, GPIO.HIGH)
        GPIO.output(STEERING_PIN, GPIO.HIGH)
        speed_pwm = GPIO.PWM(SPEED_PIN, 1000)
        speed_pwm.start(0)
        right_pwm = GPIO.PWM(RIGHT_PIN, 1150)
        right_pwm.start(0)
        left_pwm = GPIO.PWM(LEFT_PIN, 1150)
        left_pwm.start(0) 
        pygame.init()
        controller = pygame.joystick.Joystick(0)
        controller.init()

        try:
            run(speed_pwm, right_pwm, left_pwm, controller)
        finally:
            pygame.quit()	    
        
    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()
        os.system("sudo shutdown -h now")
 
 
if __name__ == '__main__':
    main()
BlackJack

@gamble: Mal kurz drüber geschaut:

`sys` wird importiert, aber nicht verwendet.

`pos` ist nicht in Grossbuchstaben wie die anderen Konstanten, und auch kein wirklich guter Name. `STEERING_FACTORS` oder `KEYBOARD_STEERING_FACTORS` würde den Wert besser beschreiben.

Warum werden die GPIO-Warnungen ausgeschaltet? Besser wäre es denen nachzugehen und sie zu vermeiden.

Der Pfad zum Joystick-Device wäre als Konstante gut aufgehoben. Dann könnte man den Code zum Testen ob es bereits eingebunden ist, auch ohne das `printed`-Flag schreiben und ohne den Pfad tatsächlich zweimal schreiben zu müssen.

``print`` ist in Python 2 keine Funktion, also sollte man das ”Argument” entweder ohne Klammern schreiben, oder durch den entsprechenden `__future__`-Import eine Funktion daraus machen. Dann gehört aber auch kein Leerzeichen zwischen Funktionsname und öffnender Klammer vom Aufruf.

`os.system()` sollte man seit dem es das `subprocess`-Modul gibt, nicht mehr verwenden.

Die `run()`-Funktion ist viel zu lang. Zu viele Zeilen und zu viele lokale Namen.

Das es zum Beispiel zwei Sätze von Namen mit Werten von gleicher Bedeutung für Tastatur und Joystick gibt, ist beispielsweise ein Hinweis, dass man Tastatur- und Joystick-Behandlung da getrennt herausziehen könnte.

Für den Joystick gibt es ”magische” Zahlen die in Kommentaren erklärt werden, wo man besser Konstanten definieren würde, also beispielsweise ``START_BUTTON = 3``.

Eine weitere ”magische” Zahl ist die 7 beim vergleich mit `step_steering` die wohl mit der Länge der `pos`-Liste zusammenhängt. Also sollte man die von ``len(pos)`` ableiten. Denn sonst bekommt man Probleme wenn man da mal mehr oder weniger Faktoren in die Liste schreiben möchte und im ganzen Programm Zahlen suchen und anpassen muss, die etwas mit der Länge der Liste zu tun haben.

Ebenfalls ”magisch” sind die Zahlen die beim Joystick entscheiden welcher Faktor verwendet wird. Die würde ich in eine Liste packen und dann per Schleife den richtigen Faktor ermitteln.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
from __future__ import absolute_import, division, print_function
import os
import subprocess
import time

import pygame
from RPi import GPIO

JOYSTICK_DEVICE_PATH = '/dev/input/js0'
PS_BUTTON = 14
START_BUTTON = 3
SELECT_BUTTON = 0

SPEED_PIN = 10
FORWARD_PIN = 11
BACKWARD_PIN = 12
STEERING_PIN = 13
RIGHT_PIN = 15
LEFT_PIN = 16
UP_PIN = 35
DOWN_PIN = 36
OPEN_PIN = 37
CLOSE_PIN = 38
READY_PIN = 40
 
CHANGE_INTERVAL = 0.1
MIN_SPEED = 55
MAX_SPEED = 100
SPEED_STEP_UP = 3
SPEED_STEP_DOWN = 5
BACKWARD, FORWARD = -1, 1
LEFT, RIGHT = -1, 1
JOYSTICK, KEYBOARD = 2, 1
STEERING_FACTORS = [0.0, 26.4, 37.9, 49.4, 63.2, 74.7, 87.4, 100.0]

 
def run(speed_pwm, right_pwm, left_pwm, controller):
    speed = 0
    speed_from_keyboard = 0
    speed_from_controller = 0
    steering = 0
    steeringleft = 0
    steeringright = 0
    steering_keyboard = 0
    steering_controller = 0
    step_steering = 0
    axis_steering = 0
    axis_direction = 0
    keyboard_direction = None
    keyboard_side = None
    requested_input = JOYSTICK
 
    while True:
        if (
            controller.get_button(PS_BUTTON)
            or pygame.key.get_pressed()[pygame.K_ESCAPE]
        ):
            return
        if controller.get_button(START_BUTTON):
            requested_input = JOYSTICK
        if controller.get_button(SELECT_BUTTON):
            requested_input = KEYBOARD
        if controller.get_axis(0) > 0.3 or pygame.key.get_pressed()[pygame.K_d]:
            GPIO.output(OPEN_PIN, GPIO.HIGH)
        else:
            GPIO.output(OPEN_PIN, GPIO.LOW)
       
        if controller.get_axis(0) < -0.3 or pygame.key.get_pressed()[pygame.K_a]:
            GPIO.output(CLOSE_PIN, GPIO.HIGH)
        else:
            GPIO.output(CLOSE_PIN, GPIO.LOW)
       
        if controller.get_axis(1) > 0.3 or pygame.key.get_pressed()[pygame.K_s]:
            GPIO.output(DOWN_PIN, GPIO.HIGH)
        else:
            GPIO.output(DOWN_PIN, GPIO.LOW)
       
        if controller.get_axis(1) < -0.3 or pygame.key.get_pressed()[pygame.K_w]:
            GPIO.output(UP_PIN, GPIO.HIGH)
        else:
            GPIO.output(UP_PIN, GPIO.LOW)
       
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    keyboard_direction = FORWARD
                elif event.key == pygame.K_DOWN:
                    keyboard_direction = BACKWARD
                elif event.key == pygame.K_LEFT:
                    keyboard_side = LEFT
                elif event.key == pygame.K_RIGHT:
                    keyboard_side = RIGHT
            elif event.type == pygame.KEYUP:
                if event.key in [pygame.K_UP, pygame.K_DOWN]:
                    keyboard_direction = None
                elif event.key in [pygame.K_LEFT, pygame.K_RIGHT]:
                    keyboard_side = None
 
#   Keyboard speed                    
        if keyboard_direction != None:
            if speed_from_keyboard * keyboard_direction < MIN_SPEED:
                speed_from_keyboard = MIN_SPEED * keyboard_direction
            elif speed_from_keyboard * keyboard_direction < MAX_SPEED:
                speed_from_keyboard += SPEED_STEP_UP * keyboard_direction
        else:
            if speed_from_keyboard > 0:
                if speed_from_keyboard > MIN_SPEED:
                    speed_from_keyboard -= SPEED_STEP_DOWN
                else:
                    speed_from_keyboard = 0
            elif speed_from_keyboard < 0:
                if abs(speed_from_keyboard) > MIN_SPEED:
                    speed_from_keyboard += SPEED_STEP_DOWN
                else:
                    speed_from_keyboard = 0
            else:
                speed_from_keyboard = 0
 
 
#   Keyboard steering
        if keyboard_side != None:
            if abs(step_steering) < len(STEERING_FACTORS) - 1:
                step_steering += keyboard_side
        else:
            if step_steering < 0:
                step_steering += 1
            elif step_steering > 0:
                step_steering -= 1
       
        if step_steering < 0:
            steering_keyboard = -1 * STEERING_FACTORS[abs(step_steering)]
        else:
            steering_keyboard = STEERING_FACTORS[abs(step_steering)]
 
#   Controller speed
        if (controller.get_axis(3) * -1.0) > 0.3:
            speed_from_controller = round(100.0 - ((1.0 - (controller.get_axis(3) * -1.0)) * 64.3), 1)
        elif controller.get_axis(3) > 0.3:
            speed_from_controller = round(100.0 - ((1.0 - controller.get_axis(3)) * 64.3), 1) * -1
        else:
            speed_from_controller = 0
 
#   Controller steering
        axis_steering = controller.get_axis(2) * 100
        if controller.get_axis(2) < 0:
            axis_direction = -1
        else:
            axis_direction = 1
 
        if abs(axis_steering) > 94:
            steering_controller = STEERING_FACTORS[7] * axis_direction
        elif abs(axis_steering) > 81:
            steering_controller = STEERING_FACTORS[6] * axis_direction
        elif abs(axis_steering) > 69:
            steering_controller = STEERING_FACTORS[5] * axis_direction
        elif abs(axis_steering) > 57:
            steering_controller = STEERING_FACTORS[4] * axis_direction
        elif abs(axis_steering) > 45:
            steering_controller = STEERING_FACTORS[3] * axis_direction
        elif abs(axis_steering) > 32:
            steering_controller = STEERING_FACTORS[2] * axis_direction
        elif abs(axis_steering) > 20:
            steering_controller = STEERING_FACTORS[1] * axis_direction
        else:
            steering_controller = 0
 
#   Which input?
        if requested_input == KEYBOARD:
            speed = speed_from_keyboard
            steering = steering_keyboard
        else:
            speed = speed_from_controller
            steering = steering_controller
 
#   steering output
        if steering < 0:
            steeringleft = 0
            steeringright = abs(steering)
        elif steering > 0:
            steeringright = 0
            steeringleft = abs(steering)
        else:
            steeringleft = 0
            steeringright = 0
 
        right_pwm.ChangeDutyCycle(steeringright)
        left_pwm.ChangeDutyCycle(steeringleft)
 
        GPIO.output(FORWARD_PIN, speed > 0)
        GPIO.output(BACKWARD_PIN, speed < 0)
        speed_pwm.ChangeDutyCycle(abs(speed))
        time.sleep(CHANGE_INTERVAL)


def main():
    if not os.path.exists(JOYSTICK_DEVICE_PATH):
        print('waiting for controller')
    while not os.path.exists(JOYSTICK_DEVICE_PATH):
        time.sleep(0.5)
    print('controller connected')

    GPIO.setwarnings(False)  # TODO Why?
    try:
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(
            [
                SPEED_PIN, FORWARD_PIN, BACKWARD_PIN, STEERING_PIN, RIGHT_PIN,
                LEFT_PIN, UP_PIN, DOWN_PIN, OPEN_PIN, CLOSE_PIN, READY_PIN,
            ],
            GPIO.OUT,
            initial=GPIO.LOW
        )
        GPIO.output([READY_PIN, STEERING_PIN], GPIO.HIGH)
        speed_pwm = GPIO.PWM(SPEED_PIN, 1000)
        speed_pwm.start(0)
        right_pwm = GPIO.PWM(RIGHT_PIN, 1150)
        right_pwm.start(0)
        left_pwm = GPIO.PWM(LEFT_PIN, 1150)
        left_pwm.start(0)

        pygame.init()
        controller = pygame.joystick.Joystick(0)
        controller.init()
        try:
            run(speed_pwm, right_pwm, left_pwm, controller)
        finally:
            pygame.quit()      
       
    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()
        subprocess.call(['sudo', 'shutdown', '-h', 'now'])
 
 
if __name__ == '__main__':
    main()

Aus der Monsterfunktion `run()` kann man mindestens drei Objekte isolieren: Eine Klasse die das Fahrzeug und seine Motorenansteuerung kapselt, und je eine Klasse für Tastatur und Joystick.

Das Programm ist momentan so geschrieben das man es per Joystick und/oder Tastatur steuern kann, es kann aber nicht mit Tastatur gesteuert werden wenn kein Joystick angeschlossen ist. Wenn man den mal nicht zur Hand hat, läuft das Programm nicht, obwohl es das eigentlich könnte, wenn man es liesse. :-)
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

ja stimmt...
ist mein erstes Projekt und mir fehlt noch das Hintergrundwissen :?
gamble
User
Beiträge: 10
Registriert: Dienstag 26. April 2016, 10:36

[codebox=jquery file=Unbenannt.js][/code]aber was ich nicht ganz verstehe:

Code: Alles auswählen

#!/usr/bin/python3

X_Button = 14
JOYSTICK_DEVICE_PATH = '/dev/input/js0'
CHANGE_INTERVAL = 0.1
STEERING_LEVELS = [0.0, 26.4, 37.9, 49.4, 63.2, 74.7, 87.4, 100.0]
speed_from_keyboard = 0

def run(speed_pwm, right_pwm, left_pwm, controller):
    while True:
        if controller.get_button(X_Button):
            return
        ... irgendwas mit speed_from_keyboard
        time.sleep(CHANGE_INTERVAL)
def main():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(
        [
            SPEED_PIN, FORWARD_PIN, BACKWARD_PIN, STEERING_PIN, RIGHT_PIN, LEFT_PIN, 
            UP_PIN, DOWN_PIN, OPEN_PIN, CLOSE_PIN, READY_PIN,
        ],
        GPIO.OUT,
        initial=GPIO.LOW
    )
    GPIO.output(READY_PIN, GPIO.HIGH)
    try:        
        controller = pygame.joystick.Joystick(0)
        controller.init()
        try:
            run(speed_pwm, right_pwm, left_pwm, controller)
        finally:
            pygame.quit()	
    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()
 
if __name__ == "__main__":
    main()
warum kennt das Skript X_Button und CHANGE_INTERVAL aber nicht "speed_from_keyboard"?
BlackJack

@gamble: Weil Du bei dem ”irgendwas” sicherlich irgendwo dem Namen etwas zuweist, und damit ist das ein lokaler Name der innerhalb der Funktion sichtbar ist und den gleichen Namen auf Modulebene verdeckt. Wenn Du innerhalb einer Funktion etwas zuweist, dann willst Du das ja aber sowieso nicht auf Modulebene zuweisen, denn das wäre dann ja eine globale Variable und damit Böse™.

`X_Button` sollte konventionell `X_BUTTON` heissen. Sonst denkt am Ende noch jemand es sei der Name einer Klasse. Auch wenn in *den* Namen normalerweise keine Unterstriche vorkommen.
Antworten