web-dev-qa-db-fra.com

pyserial - Comment lire la dernière ligne envoyée par un périphérique série

Un Arduino connecté à mon ordinateur exécute une boucle et renvoie une valeur via le port série à l'ordinateur toutes les 100 ms. 

Je souhaite créer un script Python capable de lire le port série toutes les quelques secondes seulement. Je souhaite donc qu'il ne voie que la dernière chose envoyée par l'Arduino.

Comment faites-vous cela dans Pyserial?

Voici le code que j'ai essayé et qui ne fonctionne pas. Il lit les lignes séquentiellement.

import serial
import time

ser = serial.Serial('com4',9600,timeout=1)
while 1:
    time.sleep(10)
    print ser.readline() #How do I get the most recent line sent from the device?
18
Greg

Je comprends peut-être mal votre question, mais comme il s’agit d’une ligne série, vous devez lire séquentiellement tout ce qui est envoyé depuis l’Arduino - il sera mis en mémoire tampon dans ce dernier jusqu’à ce que vous le lisiez.

Si vous souhaitez un affichage de l'état indiquant le dernier élément envoyé, utilisez un fil de discussion intégrant le code de votre question (moins le temps de veille) et conservez la dernière ligne complète lue comme dernière ligne de l'Arduino.

L'exemple de code de Update:mtasic est assez bon, mais si Arduino a envoyé une ligne partielle lorsque inWaiting() est appelé, vous obtiendrez une ligne tronquée. Au lieu de cela, ce que vous voulez faire est de placer la dernière ligne complete dans last_received et de conserver la ligne partielle dans buffer afin de pouvoir l'ajouter à la prochaine fois autour de la boucle. Quelque chose comme ça:

def receiving(ser):
    global last_received

    buffer_string = ''
    while True:
        buffer_string = buffer_string + ser.read(ser.inWaiting())
        if '\n' in buffer_string:
            lines = buffer_string.split('\n') # Guaranteed to have at least 2 entries
            last_received = lines[-2]
            #If the Arduino sends lots of empty lines, you'll lose the
            #last filled line, so you could make the above statement conditional
            #like so: if lines[-2]: last_received = lines[-2]
            buffer_string = lines[-1]

En ce qui concerne l'utilisation de readline(): Voici ce que dit la documentation Pyserial (légèrement modifié pour des raisons de clarté et avec mention de readlines ()):

Soyez prudent lorsque vous utilisez "readline". Faire spécifier un délai d'attente lors de l'ouverture du fichier port série, sinon il pourrait bloquer pour toujours si aucun caractère de nouvelle ligne n'est reçu. Notez également que "readlines ()" ne fonctionne qu'avec un délai d'attente. Il dépend d'avoir un timeout et interprète cela comme EOF (fin du fichier).

ce qui me semble tout à fait raisonnable!

28
Vinay Sajip
from serial import *
from threading import Thread

last_received = ''

def receiving(ser):
    global last_received
    buffer = ''

    while True:
        # last_received = ser.readline()
        buffer += ser.read(ser.inWaiting())
        if '\n' in buffer:
            last_received, buffer = buffer.split('\n')[-2:]

if __==  '__main__':
    ser = Serial(
        port=None,
        baudrate=9600,
        bytesize=EIGHTBITS,
        parity=PARITY_NONE,
        stopbits=STOPBITS_ONE,
        timeout=0.1,
        xonxoff=0,
        rtscts=0,
        interCharTimeout=None
    )

    Thread(target=receiving, args=(ser,)).start()
11
mtasic85

Ces solutions enlèveront le processeur en attendant les caractères.

Vous devez faire au moins un appel bloquant pour lire (1)

while True:
    if '\n' in buffer: 
        pass # skip if a line already in buffer
    else:
        buffer += ser.read(1)  # this will block until one more char or timeout
    buffer += ser.read(ser.inWaiting()) # get remaining buffered chars

... et faire la scission comme avant.

7
Rufus

Vous pouvez utiliser ser.flushInput() pour vider toutes les données série actuellement stockées dans la mémoire tampon.

Après avoir effacé les anciennes données, vous pouvez utiliser ser.readline () pour obtenir les données les plus récentes du périphérique série.

Je pense que c'est un peu plus simple que les autres solutions proposées ici. Travaillé pour moi, espérons que cela vous convient.

5
user3524946

Cette méthode vous permet de contrôler séparément le délai d'attente pour la collecte de toutes les données pour chaque ligne et un délai d'attente différent pour l'attente de lignes supplémentaires.

# get the last line from serial port
lines = serial_com()
lines[-1]              

def serial_com():
    '''Serial communications: get a response'''

    # open serial port
    try:
        serial_port = serial.Serial(com_port, baudrate=115200, timeout=1)
    except serial.SerialException as e:
        print("could not open serial port '{}': {}".format(com_port, e))

    # read response from serial port
    lines = []
    while True:
        line = serial_port.readline()
        lines.append(line.decode('utf-8').rstrip())

        # wait for new data after each line
        timeout = time.time() + 0.1
        while not serial_port.inWaiting() and timeout > time.time():
            pass
        if not serial_port.inWaiting():
            break 

    #close the serial port
    serial_port.close()   
    return lines
3
fja0568

L'utilisation de .inWaiting() dans une boucle infinie peut être problématique. Cela peut engloutir la totalité de CPU selon la mise en œuvre. Au lieu de cela, je recommanderais d'utiliser une taille spécifique de données à lire. Ainsi, dans ce cas, procédez comme suit, par exemple: 

ser.read(1024)
2
Srinath

Vous aurez besoin d’une boucle pour tout lire, avec le dernier appel de readline () bloquant jusqu’à expiration du délai. Alors:

def readLastLine(ser):
    last_data=''
    while True:
        data=ser.readline()
        if data!='':
            last_data=data
        else:
            return last_data
2
quamrana

Légère modification du code de mtasic & Vinay Sajip:

Bien que j'ai trouvé ce code très utile pour une application similaire, j'avais besoin de toutes les lignes renvoyées par un périphérique série qui enverrait des informations périodiquement.

J'ai choisi de supprimer le premier élément, de l'enregistrer, puis de rejoindre les éléments restants en tant que nouveau tampon et de continuer à partir de là.

Je réalise que c'est pas ce que Greg demandait, mais je pensais que cela valait la peine d'être partagé en tant que note secondaire.

def receiving(ser):
    global last_received

    buffer = ''
    while True:
        buffer = buffer + ser.read(ser.inWaiting())
        if '\n' in buffer:
            lines = buffer.split('\n')
            last_received = lines.pop(0)

            buffer = '\n'.join(lines)
2
Crazy Joe Malloy

Trop de complications

Quelle est la raison pour diviser l'objet d'octets par newline ou par d'autres manipulations de tableau? J'écris la méthode la plus simple, ce qui résoudra votre problème:

import serial
s = serial.Serial(31)
s.write(bytes("ATI\r\n", "utf-8"));
while True:
    last = ''
    for byte in s.read(s.inWaiting()): last += chr(byte)
    if len(last) > 0:
        # Do whatever you want with last
        print (bytes(last, "utf-8"))
        last = ''
2
LXSoft

Voici un exemple d'utilisation d'un wrapper qui vous permet de lire la dernière ligne sans processeur à 100%

class ReadLine:
    """
    pyserial object wrapper for reading line
    source: https://github.com/pyserial/pyserial/issues/216
    """
    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)

s = serial.Serial('/dev/ttyS0')
device = ReadLine(s)
while True:
    print(device.readline())
0
bdoubleu