Как сократить время, затрачиваемое на readline () из последовательных данных - PullRequest
4 голосов
/ 18 февраля 2020

Я пытаюсь создать функцию для получения компонентов гироскопа 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 = ser_io.readline() и ручным хронометром для записи на экране требуется около 2,25 с serial is open_2.
Если функция должна вызывать снова ser_io.readline() нет задержки, и линии serial is open_1 и serial is open_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]) }

со следующим результат:

    serial is open
before readline
--- 2.1859400272369385 seconds ---
after readline
before readline
--- 5.9604644775390625e-06 seconds ---
after readline
['S', '0', '0', '0', '380', '0', '-902', '-497', '-228', '200', '63', '103\r']
before readline
--- 2.86102294921875e-06 seconds ---
after readline
before readline
--- 3.814697265625e-06 seconds ---
after readline
['S', '-1', '0', '1', '375', '-8', '-918', '-497', '-223', '194', '64', '108\r']
before readline
--- 3.0994415283203125e-06 seconds ---
after readline
['S', '1', '0', '2', '380', '-10', '-909', '-500', '-223', '200', '65', '113\r']
before readline
--- 2.1457672119140625e-06 seconds ---
after readline
before readline
--- 1.9073486328125e-06 seconds ---
after readline
['S', '0', '0', '0', '379', '-1', '-914', '-500', '-220', '197', '66', '69\r']
before readline
--- 2.1457672119140625e-06 seconds ---
after readline
['S', '0', '0', '-1', '374', '-5', '-902', '-500', '-225', '1\r']
before readline
--- 3.0994415283203125e-06 seconds ---
after readline
['S', '1', '1', '1', '376', '-2', '-915', '-500', '-223', '192', '37', '75\r']

Функция занимает более двух o секунд первой итерации, остальные итерации очень быстрые.

Ответы [ 3 ]

3 голосов
/ 22 февраля 2020

У меня есть пара предложений для вас. Я пишу Windows приложения, использующие последовательный порт, и использую другой подход - я предполагаю, что принципы будут одинаковыми для всех ОС. Сначала я создаю и открываю порт в начале программы и оставляю его открытым. Хорошей практикой является закрытие порта до того, как ваша программа существует, но в этом нет особой необходимости, поскольку ОС впоследствии будет очищена.

Но ваш код будет создавать и инициализировать порт каждый раз, когда вы вызываете функцию. Вы не закрываете его явно, когда закончите; возможно, вам это сойдет с рук, потому что объект порта получает мусор. Вы доверяете последовательной библиотеке правильно закрывать порт на уровне ОС, прежде чем пытаться открыть его снова. В любом случае, если при создании объекта порта возникают накладные расходы, почему бы не выполнить его один раз и покончить с этим?

Вам вообще не нужно создавать TextIOWrapper, не говоря уже о двунаправленном. Вы задаетесь вопросом, является ли это причиной ваших проблем с производительностью, так почему бы не избавиться от этого? Библиотека последовательного порта имеет все необходимые функции: проверьте функцию read_until.

Я думаю, вам следует начать с фреймворка примерно так. Я не могу запустить и протестировать эту программу, так что это только схема c. Я удалил весь код обработки ошибок. Одна небольшая проблема заключается в том, что последовательные порты работают с байтами, и вы должны преобразовать это в строку.

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 - я делаю это все время. Три секунды близки к вечности на современном P C. Там должно быть другое объяснение этому. Не думайте, что Python является вашим узким местом.

Время событий, глядя на экран и нажимая на секундомер, неуклюже. Посмотрите на Python пакет времени. Вы можете просто напечатать значение time.time () в каждом из ваших операторов печати и убрать свой хронометр.

Вы можете проверить часть реализации сбора данных независимо. Просто разденьте логи c, чтобы проанализировать данные, и оставайтесь в то время как l oop навсегда. Распечатайте данные вместе с отметками времени для каждой полученной строки. Если у вас есть другой прибор, который взаимодействует с последовательным портом, вы можете изолировать производительность прибора от производительности программного обеспечения.

Наконец, какое событие вызывает передачу данных гироскопом? Это один из тех инструментов, который просто периодически передает свои данные, или вам нужно отправить ему какую-то команду, чтобы запросить данные? Если первое и трансляции происходят каждые три секунды, тайна разгадана; аналогично, если это последнее и задержка в ответе составляет три секунды. Я могу представить, что это может иметь место, поскольку инструмент должен будет считывать некоторые датчики и преобразовывать результаты в строку символов. Вы не показали нам всю программу и не рассказали, как работают инструменты, так что это всего лишь догадки.

2 голосов
/ 22 февраля 2020

Обратите внимание на несколько вещей, которые вы должны соблюдать.

Прежде всего, когда вы запускаете свое приложение, вы должны скопировать все ресурсы и оставить его там для использования. Последовательный порт имеет входной буфер, и, таким образом, вы можете обращаться к данным асинхронным способом, вам не нужно продолжать прислушиваться к клиенту, но, если порт закрыт, все данные, полученные до его открытия, будут отброшены и не добавляется в буфер. Вот почему вы должны оставить порт ВСЕГДА открытым.

В своих приложениях я обычно делаю абстрагирование последовательного порта и создание объекта для работы с устройством. Этот объект откроет порт и будет иметь поток Rx (нет необходимости в потоке Tx, поскольку python работает синхронно благодаря GIL) и будет следить за новыми данными. Чтобы отправить данные, вам нужно просто вызвать __send_command. Чтобы получить полученные данные, возьмите их из очереди Rx объекта.

Этот код я часто использую для связи с устройствами Serial (я работаю с промышленной автоматикой, где Serial является общим интерфейсом). Это удовлетворяет мои потребности. Я внес некоторые изменения, чтобы удалить из моего устройства определенные технические функции c (в промышленном масштабе), но оставил один как. Просто добавьте свой!

Не забудьте проверить, как часто ваш модуль отправляет данные, обычно это время может быть сконфигурировано на конкретном c регистре модуля. Обычно идет от нескольких миллисекунд до многих секунд.

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')
0 голосов
/ 21 февраля 2020

Я думаю, что вы можете использовать реализацию в c. Коды python внутренне вызывают функцию c для выполнения своей задачи. Если вы используете основные функции напрямую, это сэкономит время. Я не пробовал это в любое время. но это может помочь.

https://github.com/EveryTimeIWill18/Cython_Repo/blob/master/FastFileProcessingWithCython.ipynb

...