web-dev-qa-db-ja.com

pyserial-シリアルデバイスから送信された最後の行を読み取る方法

ループを実行しているコンピューターにArduinoを接続し、シリアルポートを介して値を100ミリ秒ごとにコンピューターに送り返します。

Python数秒ごとにシリアルポートから読み取るスクリプトを作成したいので、Arduinoから最後に送信されたものだけを表示したいです。

Pyserialでこれをどのように行いますか?

動作しないコードを試してみました。行を順番に読み取ります。

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?
19
Greg

おそらく私はあなたの質問を誤解しているかもしれませんが、シリアルラインなので、Arduinoから送信されたすべてのものを順番に読む必要があります-読むまでArduinoにバッファリングされます。

送信された最新のものを示すステータス表示が必要な場合は、質問にコードを組み込んだスレッドを使用して(スリープを除く)、最後の完全な行をArduinoから最新の行として読み取ります。

更新:mtasicのサンプルコードは非常に優れていますが、inWaiting()が呼び出されたときにArduinoが部分的な行を送信した場合、切り捨てられた行が表示されます。代わりに、最後のcomplete行をlast_receivedに入れ、bufferに部分的な行を保持して、次回のループに追加できます。このようなもの:

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]

readline()の使用について:Pyserialのドキュメントには次のように書かれています(わかりやすくするために少し編集し、readlines()に言及しています)。

「readline」を使用するときは注意してください。シリアルポートを開くときにタイムアウトを指定します。そうしないと、改行文字が受信されない場合に永久にブロックされる可能性があります。また、「readlines()」はタイムアウトでのみ機能することに注意してください。タイムアウトがあることに依存し、EOF(ファイルの終わり)として解釈します。

それは私にとって非常に合理的なようです!

29
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 __name__ ==  '__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

これらのソリューションは、キャラクターを待っている間にCPUを独占します。

Read(1)に対して少なくとも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

...そして、前と同じように分割します。

7
Rufus

ser.flushInput()を使用して、現在バッファにあるすべてのシリアルデータをフラッシュできます。

古いデータを消去した後、ser.readline()を使用して、シリアルデバイスから最新のデータを取得できます。

ここで提案されている他のソリューションよりも少しシンプルだと思います。私のために働いた、それがあなたに適していることを願っています。

6
user3524946

この方法では、各回線のすべてのデータを収集するためのタイムアウトと、追加の回線を待機するための異なるタイムアウトを個別に制御できます。

# 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

無限ループ内で.inWaiting()を使用すると問題が発生する場合があります。実装によっては [〜#〜] cpu [〜#〜] 全体がいっぱいになる場合があります。代わりに、特定のサイズのデータ​​を使用して読み取ることをお勧めします。したがって、この場合、たとえば次のことを行う必要があります。

ser.read(1024)
2
Srinath

送信されたすべてを読み取るためのループが必要になり、readline()の最後の呼び出しはタイムアウトまでブロックされます。そう:

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

MtasicおよびVinay Sajipのコードのわずかな変更:

同様のアプリケーションではこのコードは非常に役立ちましたが、定期的に情報を送信するシリアルデバイスから返される行allが必要でした。

最初の要素を上からポップして記録し、残りの要素を新しいバッファーとして再結合して、そこから続行することを選択しました。

これはnotであると理解していますが、グレッグが求めていたものですが、補足として共有する価値があると思いました。

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

複雑すぎる

改行または他の配列操作によってバイトオブジェクトを分割する理由は何ですか?私はあなたの問題を解決する最も簡単な方法を書きます:

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

CPUを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