Как написать асинхронный последовательный асинхронный считыватель Python?

#python-3.x #python-asyncio

#python-3.x #python-asyncio

Вопрос:

Я переношу некоторый код C на Python, и у меня чертовски много времени, чтобы выяснить, как использовать обработчик onReceive для последовательных байтов. Я использую

 import serial_asyncio
class Arduino: #comms is with an Arduino in serial mode)
    async def connect(self, serial_port):
        (self.serial_reader, self.serial_writer) = await 
        serial_asyncio.open_serial_connection(url=serial_port, baudrate=115200)
        print("serial opened:", serial_port)
        self.buffer = b""

    async def recv(self, data):
        self.buffer  = data
        self.process_buffer(self.buffer)

if __name__ == '__main__':
     ardunio = Arduino("/dev/ttyS0")         
     loop = asyncio.get_event_loop()
     loop.run_until_complete(ardunio.connect())
     loop.run_forever()
  

Однако я не могу понять, как исправить обработчик recv для чтения.
Я Qt, я мог бы:

 connect(amp;QAbstractSocket::readyRead, amp;Arduino::handleBytes);
  

В узле:

 arduino.on('data', line => console.log(line))
  

В Python, похоже, нет никакого очевидного ответа? Как мне получить байты последовательного порта, которые поступают для передачи в Arduino.receive (self, data)?

Ответ №1:

Однако я не могу понять, как исправить обработчик recv для чтения.

open_serial_connection это не интерфейс на основе обратного вызова, он возвращает пару потоков, которые предоставляют содержимое через сопрограммы. Это позволяет вам взаимодействовать с последовательным портом так, как если бы вы писали блокирующий код, то есть без использования обратных вызовов и буферов, которые накапливают данные. Например (непроверенный):

 async def main():
    reader, writer = await serial_asyncio.connect(url="/dev/ttyS0", baudrate=115200)
    # instead of: arduino.on('data', line => console.log(line))
    # ...we can just read some data from the serial port
    data = await reader.read(1024)
    # ...and print it right here
    print(repr(data))

asyncio.run(main())
  

Такие сопрограммы, как StreamReader.read , по-видимому, блокируют ожидание данных, но на самом деле они просто приостанавливают текущую сопрограмму и позволяют циклу событий выполнять другие действия. Это позволяет вам легко выражать тайм-ауты или выполнять другую обработку в ожидании поступления данных с последовательного порта.

Если вам все еще нужны обратные вызовы, например, потому, что вам нужно взаимодействовать с C API, у вас есть два варианта:

  • используйте create_serial_connection функцию более низкого уровня. Он принимает тип, который наследует asyncio.Protocol , где вы можете определить такие перехваты, как data_received (как обратный вызов, а не сопрограмма), что близко к тому, как вы смоделировали свой Arduino класс.

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

Примером последнего может быть:

 async def main():
    reader, writer = await serial_asyncio.connect(url="/dev/ttyS0", baudrate=115200)
    # Python equivalent of arduino.on('data', x):
    # 1. call reader.read() but don't await - instead, create a "future"
    # which works like a JavaScript Promise
    future = asyncio.ensure_future(reader.read(1024))
    # register a done callback when the result is available
    future.add_done_callback(future, lambda _: print(repr(future.result())))
    # go do something else - here we wait for an event just so main()
    # doesn't exit immediately and terminate our program
    await asyncio.Event().wait()

asyncio.run(main())
  

Но если вы не общаетесь с C, я не вижу никаких преимуществ в использовании этого стиля по сравнению с обычным async / await.

Комментарии:

1. Спасибо за это. Я все еще смущен своим предыдущим опытом асинхронной работы. Во всех предыдущих случаях я мог запрашивать чтение длины, соответствующей количеству байтов, ожидающих в буфере. Кажется, что с Python для полностью асинхронной (в общем смысле, не уверен, когда или кто отправит байты первым) связи мне просто нужно прочитать (1) в цикле? Я был сбит с толку tinkering.xyz/async-serial , из-за чего казалось, что мне пришлось проделать несколько циклов. Но я смог заставить его работать с вашей помощью! Это намного проще, чем кажется!

2. @UserOneFourTwo Я лично не использовал async-serial, но с общим asyncio вам определенно не нужно вызывать read(1) цикл. Что-то вроде await reader.read(1024) даст вам как можно больше данных, причем 1024 является верхним пределом. Но если поступает только 3 байта, вы должны получить их немедленно.

3. Спасибо. После экспериментов после того, как он заработал благодаря вашему ответу, кажется, что просто старый read() подходит.