Как полностью прочитать данные из последовательного порта? - PullRequest
0 голосов
/ 04 июля 2018

Я пытаюсь прочитать строку JSON, записанную в последовательный порт, используя следующий код на основе библиотеки PySerial:

while True:
    if serial_port.in_waiting > 0:
        buffer = serial_port.readline()
        print('buffer=', buffer)
        ascii = buffer.decode('ascii')
        print('ascii=', ascii)

Я попытался прослушать данные на порту и убедился, что данные записываются полностью без какого-либо рассеяния:

jpnevulator --ascii --tty "/dev/ttyACM1" --read
7B 22 30 22 3A 31 7D                            {"0":1}
7B 22 30 22 3A 32 7D                            {"0":2}
7B 22 30 22 3A 33 7D                            {"0":3}
7B 22 30 22 3A 34 7D                            {"0":4}
7B 22 30 22 3A 35 7D                            {"0":5}
7B 22 30 22 3A 36 7D                            {"0":6}

Однако используемый код приводит к чтению разбросанных данных и, следовательно, показывает следующие результаты:

buffer= b'{"0'
ascii= {"0
buffer= b'":1}'
ascii= ":1}
buffer= b'{"'
ascii= {"
buffer= b'0":2'
ascii= 0":2
buffer= b'}'
ascii= }

Кроме того, когда я использую read() вместо readline(), я получаю такое же поведение:

buffer= b'{'
data_str= {
buffer= b'"'
data_str= "
buffer= b'3'
data_str= 3
buffer= b'"'
data_str= "
buffer= b':'
data_str= :
buffer= b'1'
data_str= 1
buffer= b'}'
data_str= }

Я даже пытался использовать другой код , который использует ту же библиотеку, но получил ту же проблему.

Я не уверен, почему я сталкиваюсь с таким поведением.

Ответы [ 2 ]

0 голосов
/ 20 августа 2018

Это распространенная проблема при передаче данных: Когда был получен полный кадр?

Если у вас есть контроль над обеими сторонами, я бы посоветовал вам добавить дополнительные рамки в протокол. В противном случае вам нужно проверять символ за символом, если вы получили полный объект JSON.

Очень простой протокол - ndjson , который требует, чтобы вы только добавляли '\n' между кадрами и воздерживались от вставки новых строк в полезную нагрузку. Если внутри строки JSON есть действительные символы новой строки, они будут автоматически экранированы, если вы используете библиотеку JSON.

Пример автора:

# Writing ndjson frames
frames = [
    {"0": "1"},
    {"0": "2"},
]
for frame in frames:
    port.write(json.dumps(frame, separators=(',', ':')))  # No extra whitespace or newlines
    port.write('\n')  # ndjson protocol separator

Поток в формате ndjson может быть прочитан с использованием pyserial.readline().

Обратите внимание, что вам нужно указать время ожидания для выхода из вашей программы, так как readline() в противном случае будет блокироваться навсегда.

Пример чтения, предполагая, что port открывается с таймаутом:

while True:
    data = port.readline()  # Blocks until a complete frame is received or timeout
    if data:
        d = json.loads(data)
        print("Received object: %r" % d)
    else:
        if should_exit:
            break
0 голосов
/ 04 июля 2018

Я попытаюсь заняться этим. :) Ваш цикл ожидает, пока какой-либо вход станет доступным serial_port.in_waiting > 0. Отсюда и поведение, которое вы видите. Чтение начнется, как только что-нибудь может быть получено. Не похоже, что PySerial будет иметь какую-либо дополнительную доступную абстракцию, чтобы вы знали, что что-то вроде последнего готового байта будет символом фигурной скобки ASCII (я действительно только что просканировал документы). Вы всегда можете применить универсальное решение для чтения по мере того, как что-то придумано, и иметь смысл в нем внутри скрипта Python.

Сначала один вопрос. Ваш пример ввода предполагает, что вы будете иметь дело со строкой / JSON одинакового размера Должны ли мы быть действительно такими счастливчиками? Если это так, вы можете подождать, пока этот или несколько байтов станут доступными, и прочитать только нужный размер в ваш buffer.

В противном случае вариант вашего кода:

buffer = bytes()  # .read() returns bytes right?
while True:
    if serial_port.in_waiting > 0:
        buffer += serial_port.read(serial_port.in_waiting)
        try:
            complete = buffer[:buffer.index(b'}')+1]  # get up to '}'
            buffer = buffer[buffer.index(b'}')+1:]  # leave the rest in buffer
        except ValueError:
            continue  # Go back and keep reading
        print('buffer=', complete)
        ascii = buffer.decode('ascii')
        print('ascii=', ascii)

ПРИМЕЧАНИЕ 1. Я предполагаю, что serial_port.in_waiting теоретически может измениться между if и read, но я также предполагаю, что непрочитанные байты просто остаются в буфере, и мы в порядке.

ПРИМЕЧАНИЕ 2. Этот подход немного наивен и не учитывает, что вы могли также прочитать два фрагмента кода JSON.

ПРИМЕЧАНИЕ3: И это также не учитывает вложенные отображения в вашем JSON, если это так.

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


ОБНОВЛЕНИЕ: чтобы отразить обсуждение в комментариях.

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

На самом деле, если мы знаем, что строка (группа байтов), представляющая JSON, - это то, что передается через (серверы как протокол, способ инкапсуляции / представления данных (структуры)), которое может работать, но повторная сборка должна случиться над сырой последовательной связью. Наше приложение (или библиотека / модуль) может считывать необработанные последовательные данные, извлекать из них смысл и предоставлять их на более высокие уровни.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...