mit Pyserial und Python 3.3 eine zahl senden

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
DasGehtSchon
User
Beiträge: 5
Registriert: Freitag 30. November 2012, 17:12

Moin zusammen,

ich bin neu hier im Forum und auch neu bei Python.
Ich benutze Python 3.3 und Versuche mit Pyserial eine Joystick Coordinate an ein Arduino zu senden damit es das Arduino einen Servo Motor auf den enstprechenden Winkel fährt (0-180°).
Das auslesen des Joysticks und das Skalieren auf +90° und -90° habe ich hinbekommen.

Code: Alles auswählen

#-------------------------------------------------------------------------------
# Name:        Joystick Treiber -90 bis +90 grad pro Achse + Trigger 0-180 grad (XOR)
# Purpose:
#
# Author:      DasGehtSchon
#
# Created:     27.11.2012
# Copyright:   (c) DasGehtSchon 2012
# Licence:     <your licence>
#-------------------------------------------------------------------------------

# Pygame Module Importieren und Init
import pygame
pygame.joystick.init()      # Joysticks Initialiesieren
pygame.display.init()       # display Modul Initialiesieren

# Kontrolle ob ein Joystick angeschlossen ist
joyinit = pygame.joystick.get_init()
if joyinit == 1:
    print("Joysticks initialisiert")
else:
    print ("Keine Joysticks angeschlossen")
    end

# Die Anzahl der Joysticks ermitteln
joycount = pygame.joystick.get_count()
print("Es sind", joycount, "Joysticks angeschlossen")

# ein Joystick anlegen
joy1 = pygame.joystick.Joystick(1)                                             # Joystick adresse 1 anlegen
joy1.init()                                                                    # Joystick adresse 1 Initialisieren

# Informationen ueber den Initialiesierten Joystick ausgeben
print (joy1.get_name(), "wird benutzt")                                        # Der Name wie ihn das System nennt
print ("mit", joy1.get_numaxes(), "Achsen,", joy1.get_numhats(),
"Numhat und", joy1.get_numbuttons(), "Buttons")                                # Anzahl der Achsen, Numhats und Buttons

# Auf eine Bewegung der Joystick-achsen warten
def wait_input():
    while 1:
        event = pygame.event.wait()
        if (event.type == pygame.JOYAXISMOTION):                               # Es wird eine Achsbewegung erkannt
            if (event.dict['value'] > 0.1002                                   #(
            or                                                                 # um werte zittern zu vermeiden
            event.dict['value'] < -0.1002):                                    # )
                if (event.dict['axis'] == 0):                                  # Linker Stick l/R (Achse0)
                    x = event.dict['value']
                    #print("X:", int(round(x,4)*90))                           # +90 grad bis -90 grad
                    x_scale = int(round(x,4)*90)
                    print (x_scale)

                if (event.dict['axis'] == 3):                                  # rechter Stick hoch/runter (Achse3)
                    y = event.dict['value']
                    #print("Y:", int(round(y,4)*90))                           # +90 grad bis -90 grad
                    y_scale = int(round(y,4)*90)
                    print (y_scale)

            if (event.dict['axis'] == 2):                                      # LT/RT (Achse2)
                z = event.dict['value']
                #print("Z:", int(round(max(z, -z),4)*180))                     # damit beide Trigger die selbe funktion XOR haben 0-180 grad
                z_scale = int(round(max(z, -z),4)*180)
                print (z_scale)
                

            #unkomentieren zum Ausgeben
            #print("Joystick Nr.:", (event.dict['joy']))                       #Hier steht weclcher Joystick bewegt wird
            #print("Achse Nr.:", (event.dict['axis']))                         #Hier steht die nummer der Achse
            #print("Wert:", (event.dict['value']))                             #Hier steht meine Axen Coordinate (-1.0 bis 1.0)

wait_input()                                                                         # Aufruf der Wait Funktion
print("Fertig")
Ich habe es mit Serial.write() probiert es muss doch möglich sein einen int wert mit pyserial zu senden??
gibt es einen anderen befehl von Pyserial den ich übersehen hab, wie gesagt bin sehr neu bei Python

Code: Alles auswählen

import time
import serial
ser = serial.Serial("COM3", 9600)
time.sleep(1.5)

pos1 = int(180)
pos2 = int(0)


print("TX:", ser.write(pos1))
time.sleep(2)
ser.flushOutput()

print("TX:", ser.write(pos2))
time.sleep(2)
ser.flushOutput()

ser.close
Jetzt habe ich aber das Problem das mein Arduino nur einen 90° winkel an den Servo weiterleitet, wenn ich über die Cosole der Arduino IDE zahlen von 0-180 eigebe funktioniert das ganze aber.

Der Arduino Sketch sieht wie Folgt aus!

Code: Alles auswählen

#include <Servo.h>                //Servo Bibliothek laden

Servo servo;                      //Servo-Objekt erzeugen
int servoPin = 9;                 //Servo wird an Pin 9 angeschlossen
int pos = 0;                      //Variable zum Speichern der Position

void setup() {                    //Initialiesierung
  Serial.begin(9600);             //Serielle Verb. 9600 baud
  servo.attach(servoPin);         //Servo mit servoPin verbinden
}

void loop() {
  if (Serial.available()) {        //Serielle Daten vorhanden?
    pos = Serial.parseInt();       //Integer lesen
    //nur Winkel zwischen 0 und 180 auswerten.
    if ((pos >=0) && (pos <= 180)) {
      servo.write(pos);            //Position an servo senden
      delay(50);                   //Warten bis die Position erreicht wurde
      Serial.println(pos, DEC);    // Position bestaetigen
    }
    else 
        {
         Serial.print("Ungueltiger Winkel"); //Ungueltige eingabe ausgeben
         Serial.println("");                 //Neue Zeile
        }
   }
}
Ich bin für jede Hilfe dankbar falls jemand vorschläge oder besser noch erfahrung mit pyserial und Arduino hat.

PS: Die Beispiele hier sind mit Python 2.x umgesetzt und ich vermute das es an Python 3.x liegt


Mfg
DasGehtSchon
BlackJack

@DasGehtSchon: Das kann ich kaum glauben dass das der Quelltext ist und Du keine Ausnahme bekommst, wenn `Serial.write()` erwartet keine `int`\s sondern Byteketten und sollte einen `int`-Wert mit einem `TypeError` quittieren.

Du möchtest eine Zeichenkette beziehungsweise eine Bytekette mit den ASCII-Werten senden, welche die Zahl repräsentieren. Und die mit einem Zeilenende-Bytewert abschliessen der auch gesendet wird, denn das entspräche dann dem was Du in der IDE über die serielle Konsole schickst. Für Python 3 bedeutet das `int` in Zeichenkette (`str`) umwandeln und ein Zeilenende anhängen, am besten mit der `format()`-Methode auf Zeichenketten, und die dann in eine Bytekette (`bytes`) umwandeln und *das* dann mit `write()` senden.

Der Code auf der verlinkten Webseite unterscheidet sich recht grundlegend von Deinem vorgehen, da dort die Werte als Bytewerte gesendet werden und nicht als ASCII-Repräsentation von Bytewerten.

Der Umweg über das `dict`-Attribut von den `Event`-Objekten von Pygame ist reichlich eigenartig. Statt ``event.dict['value']`` würde man normalerweise einfach ``event.value`` schreiben. Ebenfalls untypisch sind die unnötigen Klammern um die Ausdrücke bei den Bedingungen bei ``if``.

Wenn kein Joystick angeschlossen ist wird das Programm mit einem Fehler aussteigen, da `end` ein nicht definierter Name ist.

Bei dem ersten ``if`` zu `event.value` könnte man die `abs()`-Funktion verwenden. Die Funktion ist wohl auch verständlicher als ein ``max(z, -z)``.

Was das runden der Zwischenergebnisse bewirken soll ist mir nicht so ganz klar.

Die Zeilen sind durch die Kommentare viel zu lang. Das kann man so nur sehr schwer lesen, weil die Kommentare so gut wie alle in die nächste Zeile umgebrochen werden und so eine Art „Kamm-Effekt” entsteht. Der PEP 8 -- Style Guide for Python Code empfiehlt maximal 79 Zeichen pro Zeile.
DasGehtSchon
User
Beiträge: 5
Registriert: Freitag 30. November 2012, 17:12

Danke für die schnelle Antwort,
@BlackJack: der Code mit dem 'int' führt zu keiner Fehlermeldungwenn ich ihn ausführe!?
Das mit dem runden ist scheinbar noch aus der phase drinn geblieben als ich ein bischen mit den Werten gespielt habe, sollte da eigentlich nicht sein, hab ich vergessen zu löschen :o
Das mit dem dict attribut hab ich so aus dem Beispiel übernommen :roll: , wie gesagt ist eigentlich mein Erstes Programm was ich in Python schreibe, ausser diverse Tutorials nachzutippen halt.

Und das mit den Kommentaren fand ich auf meinem Großen Monitor eigentlich schön übersichtlich :D

Allerdings verstehe ich die format() methode irgendwie nicht :K :cry:
könnte mir jemand evtl, mal ein Beispiel schreiben wie ich einen 'int' wert von zb 180 so formatieren kann das er von Serial.write() akzeptiert wird? bzw. so formatiert wird wie BlackJack beschrieben hat.

So geht es scheinbar nicht.

Code: Alles auswählen

pos1 = int(180)
pos2 = int(0)

pos1_str = str(pos1)+"\n"
pos1_form = "{0}".format(pos1_str)
print(pos1_form)
pos1_enc = pos1_form.encode('UTF-8')
print(pos1_enc)
Mfg
DasGehtSchon
BlackJack

@DasGehtSchon: Wenn das bei Dir keine Ausnahme ergibt verwendest Du wahrscheinlich Windows, da kann man die `write()`-Methode mit einer ganzen Zahl als Argument aufrufen die als Anzahl von Nullbytes interpretiert wird, die dann übertragen werden. Was nicht dokumentiert und IMHO auch ziemlich unsinnig ist. Der Test der das verhindern würde steht noch im Quelltext, ist aber auskommentiert. In der Posix-Variante (Linux, MacOS, …) kann man keine Zahl übergeben.

Einen grossen Monitor habe ich auch, aber zum einen gibt es Umgebungen bei denen 80 Zeichen Standard sind — zum Beispiel Terminals, und zum anderen leidet die Lesbarkeit bei zu langen Zeilen, weil man den Anfang der Folgezeile leichter aus den Augen verliert. LaTeX setzt Zeilen deswegen zum Beispiel normalerweise mit cirka 66 Zeichen pro Zeile. Grosser Monitor, insbesondere in der Breite, bedeutet ja nicht zwangsläufig, dass man den Editor in die Breite zieht, sondern dass man mehr nebeneinander darstellen kann. Neben dem Textbereich können noch diverse andere Anzeigen und Dokumentation platz finden, und/oder ein Terminal zum ausführen, oder ein Webbrowser mit Dokumentation oder dem Forum hier.

Der `int()`-Aufruf bei 180 und 0 macht keinen Sinn. Das sind schon ganze Zahlen. Die werden durch `int()` nicht ”ganzzahliger”.

So wie Du es machst ist es teilweise unsinnig, aber so sollte es gehen. Beschreibe mal was der Ausdruck hinter `pos1_form` bewirkt.
DasGehtSchon
User
Beiträge: 5
Registriert: Freitag 30. November 2012, 17:12

@BlackJack: Ja ich verwende Windows.

Wenn ich es richtig verstanden habe dient '{0}' als platzhalter für eine Variable die dann mit entsprechenden formatierungen im 'str' abgelegt wird.

Wenn ich diese dann zb so

Code: Alles auswählen

pos1 = 180

pos1_str = str(pos1)+"\n"
print(pos1_str)
pos1_form = "{0}".format(pos1)+"\n"
print(pos1_form)
benutze sieht es für mich nach dem selben ergebnis aus.

wenn ich diese dann an 'Serial.write()' übergebe meckert er "TypeError: string argument without en encoding"

Code: Alles auswählen

pos1_enc = pos1_form.encode('UTF-8')
print(pos1_enc)
überträgt er dann scheinbar, er meckert jedenfalls nicht.
Aber der Servo bewegt sich nicht.

Mfg
DasGehtSchon
BlackJack

@DasGehtSchon: Die Erklärung von `format()` stimmt, aber warum machst Du das an der Stelle? Was ist der Sinn davon? Was für einen Typ hat das Argument von `format()` in Deinem Aufruf und was ist das Ergebnis?

Wenn Du `pos1_enc` an den Arduino sendest, dann sollte es eigentlich funktionieren.
DasGehtSchon
User
Beiträge: 5
Registriert: Freitag 30. November 2012, 17:12

So also mit Python 2.7 funktioniert es auf anhieb so

Code: Alles auswählen

import serial
import time

ser = serial.Serial("COM3", 9600)
time.sleep(2)

pos1 = 180
pos2 = 0
pos3 = 90

ser.write(pos1)
ser.flush()
print ser.readline()
    
ser.write(pos2)
ser.flush()
print ser.readline()

ser.write(pos3)
ser.flush()
print ser.readline()

ser.close()
hmm liegt es evtl an Pyserial 2.6 und Python 3.3?

Mfg
DasGehtSchon
BlackJack

@DasGehtSchon: *Nochmal*: `write()` ist nicht für die Übergabe von ganzen Zahlen gedacht und in älteren Versionen (z.B. 1.35) von `serial` gibt das auch für das Windowsmodul einen `TypeError` und in der aktuellen Version funktioniert das nur in Python 2.x, und nur unter Windows, und das auch nur *zufällig*, weil der Autor den Test auskommentiert hat. Was ich einfach mal für einen Fehler halte, weil es nicht dokumentiert ist und eben nur in dieser Konstellation funktioniert.

Wenn man die Zahlen in Zeichenketten (und in Python 3 dann noch in Byteketten) umwandelt, sollte das funktionieren.
DasGehtSchon
User
Beiträge: 5
Registriert: Freitag 30. November 2012, 17:12

Also es funktioniert, Problem war die 'sleep' time nach dem Verbinden

Code: Alles auswählen

ser = serial.Serial("COM3", 9600)
time.sleep(2)
am anfang hatte ich 1.5s weil ich das irgendwo gelesen habe, das der Arduino solange benötigt um die Verbindung aufzubauen,
aber scheinbar braucht mein Arduino ein bischen länger, warum auch immer wenn ich die zeit auf 2s stelle funktioniert es mit der 'pos1_enc'

Vielen dank für die Hilfe

Mfg
DasGehtSchon
BlackJack

@DasGehtSchon: Der Verbindungsaufbau dauert nicht so lange sondern in der ersten Sekunde (oder anscheinend auch etwas länger) hört der Bootloader mit ob man nicht eventuell über die Verbindung neuen Code auf den Chip flashen möchte. Ist jedenfalls beim aktuellen Uno R3 der Fall.
Antworten