web-dev-qa-db-fra.com

Lire des données série en temps réel dans Python

J'utilise un script dans Python pour collecter des données à partir d'un microcontrôleur PIC via un port série à 2 Mbit/s.

Le PIC fonctionne avec une synchronisation parfaite à 2 Mbps, ainsi que le port série usb FTDI à 2 Mbps (tous deux vérifiés avec un oscilloscope)

J'envoie des messages (taille d'environ 15 caractères) environ 100 à 150 fois par seconde et leur nombre augmente (pour vérifier si des messages sont perdus, etc.)

Sur mon ordinateur portable, Xubuntu est exécuté en tant que machine virtuelle. Je peux lire le port série via PuTTY et via mon script (python 2.7 et pySerial).

Le problème:

  • Lors de l'ouverture du port série via PuTTY, je vois tous les messages (le compteur dans le message incrémente 1 par 1). Parfait!
  • Lors de l'ouverture du port série via pySerial, je vois tous les messages mais au lieu de recevoir 100 à 150 fois par seconde, je les reçois à environ 5 par seconde (le message incrémente encore 1 par 1), mais ils sont probablement stockés dans une mémoire tampon comme lorsque je l'éteins le PIC, je peux aller à la cuisine et revenir et je reçois toujours des messages.

Voici le code (j'ai omis la plus grande partie du code, mais la boucle est la même):

ser = serial.Serial('/dev/ttyUSB0', 2000000, timeout=2, xonxoff=False, rtscts=False, dsrdtr=False) #Tried with and without the last 3 parameters, and also at 1Mbps, same happens.
ser.flushInput()
ser.flushOutput()
While True:
  data_raw = ser.readline()
  print(data_raw)

Tout le monde sait pourquoi pySerial prend autant de temps à lire du port série jusqu'à la fin de la ligne? De l'aide?

Je veux avoir cela en temps réel.

Merci

32
Vasco Baptista

Vous pouvez utiliser inWaiting() pour obtenir le nombre d'octets disponibles dans la file d'attente d'entrée.

Ensuite, vous pouvez utiliser read() pour lire les octets, quelque chose comme ça:

While True:
    bytesToRead = ser.inWaiting()
    ser.read(bytesToRead)

Pourquoi ne pas utiliser readline()dans ce cas de Docs:

Read a line which is terminated with end-of-line (eol) character (\n by default) or until timeout.

Vous attendez le délai d’attente à chaque lecture car il attend eol. l’entrée série Q reste la même qu’il reste beaucoup de temps pour arriver à la "fin" de la mémoire tampon, pour mieux la comprendre: vous écrivez sur l’entrée Q comme une voiture de course et lisez comme une vieille voiture :)

34
Kobi K

Vous devez définir le délai d'attente sur "Aucun" lorsque vous ouvrez le port série:

ser = serial.Serial(**bco_port**, timeout=None, baudrate=115000, xonxoff=False, rtscts=False, dsrdtr=False) 

Ceci est une commande bloquante, vous attendez donc de recevoir à la fin des données contenant une nouvelle ligne (\ n ou\r\n): line = ser.readline ()

Une fois que vous avez les données, il retournera dès que possible.

4
Fabian Meier

De le manuel :

Valeurs possibles pour le paramètre timeout:… x définissez timeout sur x secondes

et

readlines (sizehint = None, eol = '\ n') Lit une liste de lignes jusqu'à l'expiration du délai. sizehint est ignoré et n'est présent que pour la compatibilité de l'API avec les objets File intégrés.

Notez que cette fonction ne renvoie que le délai d'expiration.

Ainsi, votre readlines reviendra au maximum toutes les 2 secondes. Utilisez read() comme suggéré par Tim.

2
msw

Une très bonne solution à cela peut être trouvée ici :

Voici une classe qui sert de wrapper à un objet pyserial. Il vous permet de lire des lignes sans 100% de CPU. Il ne contient aucune logique de délai d'attente. En cas d'expiration du délai, self.s.read(i) renvoie une chaîne vide et vous pouvez générer une exception pour indiquer le délai.

Il est également supposé être rapide selon l'auteur:

Le code ci-dessous m'apporte 790 Ko/s lors du remplacement du code par la méthode readline de pyserial, ce qui ne m'apporte que 170 Ko/s.

class ReadLine:
    def __init__(self, s):
        self.buf = bytearray()
        self.s = s

    def readline(self):
        i = self.buf.find(b"\n")
        if i >= 0:
            r = self.buf[:i+1]
            self.buf = self.buf[i+1:]
            return r
        while True:
            i = max(1, min(2048, self.s.in_waiting))
            data = self.s.read(i)
            i = data.find(b"\n")
            if i >= 0:
                r = self.buf + data[:i+1]
                self.buf[0:] = data[i+1:]
                return r
            else:
                self.buf.extend(data)

ser = serial.Serial('COM7', 9600)
rl = ReadLine(ser)

while True:

    print(rl.readline())
0
Joe