Python 如何减少花费在';readline()';从串行数据

Python 如何减少花费在';readline()';从串行数据,python,python-3.x,performance,serial-port,Python,Python 3.x,Performance,Serial Port,我试图制作一个函数,从传感器获取陀螺仪组件X,Y,Z 功能如下: def bimu_get_gyroscope_raw(): #ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=15) ser = serial.Serial('/dev/tty.usbserial-00002014', 115200, timeout=15) ser_io = io.TextIOWrapper(io.BufferedRWPair(ser

我试图制作一个函数,从传感器获取陀螺仪组件X,Y,Z

功能如下:

def bimu_get_gyroscope_raw():
    #ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=15)
    ser = serial.Serial('/dev/tty.usbserial-00002014', 115200, timeout=15)
    ser_io = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1),  
                               newline = '\r',
                               line_buffering = True)
    try:
        ser.isOpen()
        print('serial is open')
    except:
        print('error_1')
        exit()
    #--------------------
    i = 0
    gyro_dict = dict()
    if (ser.isOpen()):
        ser.flushInput()
        # write the function to get 
        while (i==0):
            try:
                print('serial is open_1')
                line = ser_io.readline()
                print('serial is open_2')
                print(line)
            except serial.SerialException as err:
                print("Error ocurred while reading data: {}".format(err))
            if not line.endswith('\r'):
                print("Attempt to read from serial port timed out ... Exiting.")
                break  # terminate the loop and let the program exit
            if line.startswith('S,'):
                i += 1
                line = line.split(',')
                print(line)
                if len(line)==12: 
                    gyro_dict = {'x':float(line[1]), 'y': float(line[2]), 'z': float(line[3]) }
    else:
        print('Cannot open serial port')
    return gyro_dict
我得到以下输出:

raw = bimu_get_gyroscope_raw()
print(raw)

serial is open
serial is open_1
-43,-122,-2833,83,65
serial is open_2
serial is open_1
S,2,0,0,-20,19,1014,-146,184,-158,168,99
serial is open_2
['S', '2', '0', '0', '-20', '19', '1014', '-146', '184', '-158', '168', '99\r']
{'z': 0.0, 'y': 0.0, 'x': 2.0}
我遇到的问题是,在我第一次调用line
line=ser_io.readline()
期间,使用手动计时表在屏幕上写大约需要2.25秒
serial处于打开状态\u 2

如果函数需要再次调用ser_io.readline(),则不会出现延迟,并且会导致
序列号打开\u 1
序列号打开\u 2
几乎同时出现

我认为,对
readline()
的第一次调用在内部对端口或数据缓冲区做了一些事情,一旦完成,对
readline()
的连续调用就会运行得更快

有没有办法解决这个问题,让函数始终快速运行

编辑

我已经使用
time
python模块测试了多次,并修改了readline部分,如下所示:

     while (i<=5):
        try:
            print('before readline')
            start_time = time.time()
            line = ser_io.readline()
            #print(line)
            print("--- %s seconds ---" % (time.time() - start_time))
            #print(line)
            print('after readline')
        except serial.SerialException as err:
            print("Error ocurred while reading data: {}".format(err))
        if not line.endswith('\r'):
            print("Attempt to read from serial port timed out ... Exiting.")
            break  # terminate the loop and let the program exit
        if line.startswith('S,'):
            i += 1
            line = line.split(',')
            print(line)
            if len(line)==12: 
                gyro_dict = {'x':float(line[1]), 'y': float(line[2]), 'z': float(line[3]) }

该函数在第一次迭代时需要两秒钟以上,其余的迭代速度非常快。

我认为使用c语言实现可能会有所帮助。python代码在内部调用c函数来执行其任务。如果直接使用底层函数,将节省时间。 我从来没有试过。但这可能会有所帮助


我有几个建议给你。我编写使用串行端口的Windows应用程序,我使用不同的方法——我假设所有操作系统的原理都是相同的。我首先在程序开始时创建并打开端口,然后让它保持打开状态。在程序存在之前关闭端口是一种很好的做法,但这并不是必须的,因为操作系统会在程序存在之后进行清理

但是每次调用函数时,代码都会创建并初始化端口。完成后,您没有明确关闭它;也许您可以不受影响,因为端口对象会被垃圾收集。您信任串行库在再次尝试打开之前在操作系统级别正确关闭端口。在任何情况下,如果在创建端口对象时有开销,为什么不引发它一次并处理它呢

您根本不需要创建TextIOWrapper,更不用说双向的了。您想知道这是否是您的性能问题的原因,为什么不摆脱它呢?串行端口库具有您所需的所有功能:检查
read\u until
功能

我认为你应该从这样一个框架开始。我不能运行和测试这个程序,所以它只是一个示意图。我已经去掉了所有的错误处理代码。一个小问题是串行端口以字节为单位工作,您必须将其转换为字符串

ser = serial.Serial('/dev/tty.usbserial-00002014', 115200, timeout=15)
def bimu_get_gyroscope_raw():
    while True:
        ser.flushInput()
        b = ser.read_until('\r')
        s = str(b, encoding='latin1')  # convert to str
        if a.startswith('S,'):
            line = s.split(',')
            if len(line)==12: 
                return dict(x = float(line[1]),
                            y = float(line[2]),
                            z = float(line[3]))
我已将
ser
设置为全局变量,但您也可以将其作为参数传递给函数

记住串行端口在现代操作系统上是如何工作的。您永远不会直接从硬件读取字符-操作系统会为您这样做,并将字符放在输入缓冲区中。当您从端口“读取”时,实际上是在从缓冲区中检索任何字符,或者等待它们的到来。你所观察到的——一个长的延迟,然后是一连串的数据线——可以用陀螺仪硬件在几秒钟内什么都不做,然后产生一个超过一行长的数据突发来解释。我不知道你的陀螺仪是怎么工作的,所以我不能说这是真的

PySerial实现实际上是一组操作系统调用的包装器。Python的开销非常小,大部分是错误处理代码。我相信,使用Python,您将能够每秒接收数千个字符——我一直都在这样做。在现代个人电脑上,三秒几乎是永恒的。这肯定有另一种解释。不要认为Python是你的瓶颈

通过查看屏幕并单击秒表来计时事件是很笨拙的。看看Python时间包。您只需在每个打印语句中打印time.time()的值,然后将计时器放在一边

您可以独立测试实现的数据收集部分。只需去掉解析数据的逻辑,永远停留在while循环中。打印每个接收行的数据和时间戳。如果您有另一台与串行端口通信的仪器,您可以将仪器的性能与软件的性能隔离开来


最后,是什么事件导致陀螺仪进行数据传输?它是定期广播数据的仪器之一,还是您必须向它发送一些命令来请求数据?如果前者和广播是每三秒一次,谜团就解开了;同样,如果是后者,响应中的延迟为3秒。我可以想象这种情况可能会发生,因为仪器必须读取一些传感器并将结果转换为字符串。您没有向我们展示整个程序,也没有告诉我们仪器是如何工作的,所以这只是猜测。

注意一些您应该观察的事情

首先,当您启动应用程序时,您应该实例化所有资源并将其保留在那里以供使用。 串行端口有一个输入缓冲区,因此,您可以以一种同步方式访问数据,您不需要一直监听客户端,但是,如果端口关闭,则在打开之前接收到的所有数据都将被丢弃,而不会添加到缓冲区。这就是为什么你必须让港口一直开着

我在应用程序中通常做的是抽象串行端口并创建一个对象来处理设备。 这个对象将打开端口并有一个Rx线程(不需要Tx线程,因为python是以同步方式运行的)
ser = serial.Serial('/dev/tty.usbserial-00002014', 115200, timeout=15)
def bimu_get_gyroscope_raw():
    while True:
        ser.flushInput()
        b = ser.read_until('\r')
        s = str(b, encoding='latin1')  # convert to str
        if a.startswith('S,'):
            line = s.split(',')
            if len(line)==12: 
                return dict(x = float(line[1]),
                            y = float(line[2]),
                            z = float(line[3]))
import serial    # pip install pyserial
import io
import time
import threading


class SerialDevice():

    def __init__(self, baudrate=9600, port='COM1', timeout=0.1):
        self.ser = serial.Serial()
        self.ser.baudrate = baudrate
        self.ser.port = port
        self.ser.timeout = timeout
        self.sio = io.TextIOWrapper(io.BufferedRWPair(self.ser, self.ser), newline='\r\n')
        self.received_data = []


    def connect(self):
        try:
            self.ser.open()
            time.sleep(0.1)
            self.clear_data()
            self.__control_thread = threading.Thread(target=self.__receive_handler, args=())
            self.__control_thread.start()
        except Exception as e:
            print(e)
            return False
        return self.ser.is_open


    def disconnect(self):
        try:
            if(self.ser.is_open):
                self.ser.close()
                return True
            else:
                return False
        except Exception as e:
            print(e)
            return False


    def connected(self):
        return self.ser.is_open


    def data_available(self):
        return len(self.received_data)


    def get_data(self):
        '''Pop the first item from the received data list'''
        if(len(self.received_data)):
            return self.received_data.pop(0)
        return None


    def peek_data(self):
        '''Pop the first item from the received data list'''
        if(len(self.received_data)):
            return self.received_data[0]
        return None


    def clear_data(self):
        '''Clear the received data list'''
        self.received_data.clear()


    def __receive_handler(self):
        while(not self.ser.is_open): # Waits for the port to open
            time.sleep(0.1)
        # Clear serial input buffer
        self.ser.read(self.ser.in_waiting)
        while(self.ser.is_open):
            try:
                if(self.ser.in_waiting):
                    data = self.ser.readline()
                    self.received_data.append(self.__unpack_data(data))
                    print('received! {}'.format(data))
            except Exception as e:
                print(e)
            time.sleep(0.001)


    def __unpack_data(self, data=''):
        '''Unpacks the received data to the measurement format
        Receives the binary array and returns a Measurement object'''
        # Decode the received data here and return it processed as an object to the received_data queue
        # in this case I'll just return the same daata
        return data



    def __send_command(self, command):
        # send the command using serial port
        # Return 1 if success, 0 if error
        try:
            if(self.ser.is_open):
                self.ser.write((command + '\r\n').encode('ascii'))
                return 1
        except Exception as e:
            print(e)
        return 0


# this is a helper function to send commands to your device
    def send_global_reset(self):
        '''Global reset to reset all menu settings to the original factory defaults'''
        return self.__send_command('Esc R')