GithubHelp home page GithubHelp logo

johannchangpro / aioserial.py Goto Github PK

View Code? Open in Web Editor NEW
122.0 10.0 14.0 47 KB

pyserial-asyncio for humans.

Home Page: https://pypi.org/project/aioserial/

License: Mozilla Public License 2.0

Python 100.00%
asyncio library python serial asynchronous

aioserial.py's Introduction

aioserial

A Python package that combines asyncio and pySerial.

Quick start

A simple serial port reader

import asyncio

import aioserial


async def read_and_print(aioserial_instance: aioserial.AioSerial):
    while True:
        print((await aioserial_instance.read_async()).decode(errors='ignore'), end='', flush=True)

asyncio.run(read_and_print(aioserial.AioSerial(port='COM1')))

pyserial-asyncio example replacement

The example usage from pyserial-asyncio

https://pyserial-asyncio.readthedocs.io/en/latest/shortintro.html

import asyncio
import serial_asyncio

class Output(asyncio.Protocol):
    def connection_made(self, transport):
        self.transport = transport
        print('port opened', transport)
        transport.serial.rts = False  # You can manipulate Serial object via transport
        transport.write(b'Hello, World!\n')  # Write serial data via transport

    def data_received(self, data):
        print('data received', repr(data))
        if b'\n' in data:
            self.transport.close()

    def connection_lost(self, exc):
        print('port closed')
        self.transport.loop.stop()

    def pause_writing(self):
        print('pause writing')
        print(self.transport.get_write_buffer_size())

    def resume_writing(self):
        print(self.transport.get_write_buffer_size())
        print('resume writing')

loop = asyncio.get_event_loop()
coro = serial_asyncio.create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200)
loop.run_until_complete(coro)
loop.run_forever()
loop.close()

aioserial equivalence

import asyncio

import aioserial


async def read_and_print(aioserial_instance: aioserial.AioSerial):
    while True:
        data: bytes = await aioserial_instance.read_async()
        print(data.decode(errors='ignore'), end='', flush=True)
        if b'\n' in data:
            aioserial_instance.close()
            break

aioserial_instance: aioserial.AioSerial = aioserial.AioSerial(port='/dev/ttyUSB0', baudrate=115200)
asyncio.run(asyncio.gather(read_and_print(aioserial_instance), aioserial_instance.write_async(b'Hello, World!\n')))

API

AioSerial

>>> import aioserial
>>> import serial

>>> isinstance(aioserial.AioSerial(), serial.Serial)
True

>>> issubclass(aioserial.AioSerial, serial.Serial)
True

>>> aioserial.Serial is serial.Serial
True

Constructor

aioserial_instance: aioserial.AioSerial = aioserial.AioSerial(
    # ... same with what can be passed to serial.Serial ...,
    loop: Optional[asyncio.AbstractEventLoop] = None,
    cancel_read_timeout: int = 1,
    cancel_write_timeout: int = 1)

Methods

read_async
bytes_read: bytes = \
    await aioserial_instance.read_async(size: int = 1)
read_until_async
at_most_certain_size_of_bytes_read: bytes = \
    await aioserial_instance.read_until_async(
        expected: bytes = aioserial.LF, size: Optional[int] = None)
readinto_async
number_of_byte_read: int = \
    await aioserial_instance.readinto_async(b: Union[array.array, bytearray])
readline_async
a_line_of_at_most_certain_size_of_bytes_read: bytes = \
    await aioserial_instance.readline_async(size: int = -1)
readlines_async
lines_of_at_most_certain_size_of_bytes_read: bytes = \
    await aioserial_instance.readlines_async(hint: int = -1)
write_async
number_of_byte_like_data_written: int = \
    await aioserial_instance.write_async(bytes_like_data)
writelines_async
number_of_byte_like_data_in_the_given_list_written: int = \
    await aioserial_instance.writelines_async(list_of_bytes_like_data)

Other APIs

All the other APIs in the mother package pySerial are supported in aioserial as-is.

Why aioserial?

aioserial.py's People

Contributors

mrjohannchang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aioserial.py's Issues

Add tests

I think a good proof, that the code works fine under multiple os and Python versions, tests should be added.
As well they help for future contributions.

How to process incoming serial data

aioserial is an excellent package for writing and reading data from serial ports. Thanks for creating it!

I have a serial device I am communicating with that can receive commands and respond to those commands. For example, I can write {get_temperature} to the serial port and the serial device will respond back with the current temperature. The aioserial package handles this task elegantly.

My serial device will also send back data to indicate that a button, for example, has been pushed on the device. This data is sent back asynchronously without being initiated by a command from the client computer. I think it's just my ignorance of asyncio, but I'm trying to figure out how to receive and process these button push events arriving from the serial device while still being able to send specific commands and receive responses from the serial device.

Here's the code I've written so far for getting temperature:

import asyncio
from aioserial import AioSerial

PORT = "COM4"
BAUDRATE = 115_200

serial = AioSerial(port=PORT, baudrate=BAUDRATE, timeout=5.0)

async def send_command_await_response(command):
    command_encoded = bytes(command, encoding='utf8')
    await serial.write_async(command_encoded)
    result = await serial.readline_async()
    return result.rstrip().decode()

async def main():
    command = "{get_temperature}"
    response = await send_command_await_response(command)
    print(f"{response=}")


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

Can you share some insights into how I could use aioserial to accomplish my objective?

Flush unread data

Hello,

Is there a way to discard unread data? My use case is that I send a command to a device, read the first line, an then discard all the data it sends until I send more data.

Thanks!

setting and clearing RTS seems not to work

Hi,
I'm just about using your library. And it works fine so far, thank you.
However, i do not succeed in setting / clearing the RTS.
I need this to reset a esp8266 board.

the original esptool.py works like this:

    def hard_reset(self):
        self._setRTS(True)  # EN->LOW
        time.sleep(0.1)
        self._setRTS(False)

but when i do this with your lib, this seems not to do anything :-(
any idea?
python 3.8
aioserial 1.2.3
pyserial 3.4

aio1= aioserial.AioSerial(port='/dev/ttyUSB0', baudrate='115200' )
print("reset rts ")
aio1.rts=False
await asyncio.sleep(0.1)
aio1.rts=True

aio1.is_open -> this one seems to be correct and usable

Support for opening via `serial_for_url`

Most of my serial ports are opened by the serial_for_url factory function.

I find that the hwgrep and spy urls are invaluable for bringup and debugging.
hwgrep is also quite valuable on attaching to specific hardware.

Do have any thoughts on extending these url based classes into your library.
perhaps an async_serial_for_url function

I don't see an easy way without a metaclass to add in the mixin for overriding the port function.

`cancel_read()` does not work

Seems like aioserial.cancel_read() does not work. It might be that I am not aware of some concurrency rule(s), as I am quite inexperienced with asyncio/aioserial.

Here is the minimum steps to reproduce:

import asyncio
import time

import aioserial


async def read_and_print(port: aioserial.AioSerial):
    while True:
        try:
            print("Reading....")
            data: bytes = await port.read_async()
            print(data, flush=True)
        except asyncio.CancelledError:
            print("Cancelled")
            break
        except Exception:
            print("Other exception?")
            break


async def wait_and_cancel(port: aioserial.AioSerial):
    await asyncio.sleep(1)
    print("Cancel request!")
    port.cancel_read()


async def main():
    port: aioserial.AioSerial = aioserial.AioSerial(port="COM6", baudrate=115200, timeout=5)

    start = time.perf_counter()
    await asyncio.gather(read_and_print(port), wait_and_cancel(port))
    end = time.perf_counter()
    print(f"Duration: {end-start:3f}")


if __name__ == "__main__":
    asyncio.run(main())

Read timeout is intentionally set to 5 sec, while it should be canceled by cancel_read() after 1 sec.
I would expect first cancel_read() to throw exception and break out of read_and_print(), something like this:

Reading....
Cancel request!
Cancelled
Duration: 1.001

Instead, nothing happens and loop is continuing:

Reading....
Cancel request!
b''
Reading....
b''
Reading....
b''
Reading....
b''

...

I've tested this with non-async version of the same code, with aioserial (read() running in thread, cancel_read() in main), and it works, so it must be something related to async stuff.

Any help is very appreciated.

class error

Hi~ nice work.

in python 3.7 and virtual env, i got some error here:

import aioserial
import serial

 class AioSerial(serial.Serial):
AttributeError: module 'serial' has no attribute 'Serial'

有读写的例程吗?

运行了simple example,串口接收不到什么东西啊。

`async def read_and_print(aioserial_instance: aioserial.AioSerial):
while True:
print((await aioserial_instance.read_async()).decode(errors='ignore'), end='', flush=True)

asyncio.run(read_and_print(aioserial.AioSerial(port='COM35')))
`

希望能简单演示一下

Collaboration

Hello! I was just about to open a new library and was checking asyncio naming conventions when I found aioserial.

I have not reviewed this repository extensively, but my initial impression is that it uses ThreadPoolExecutors to wrap the pySerial library.

My work with pySerial has focused on reworking its backend at the OS level to support asyncio with no need to create new threads. This, I believe, is a more elegant solution that fits with the OS API intended use and the "single thread spirit" of asyncio.

So far I have a working asyncio implementation using the Windows API. I am neither a Linux nor Windows developer, but more comfortable on Linux, and my gut feeling is that async serial will be supported by Linux APIs more easily than Windows.

Let me know if you would be interested in my contributing to this repository with the intention of replacing thread pools with native OS event signaling.

  • J.P.

Data never received

Hi,
I am trying to do something fairly simple. I have a serial device (a micro:bit) that when I send it a 'g' followed by a carriage return, it returns a string of data.

Using a terminal emulator, If I do this, here is the string I get back:
16,-96,-1008,False,False,0,0,0,9,9,5

The data is generated via a Micropython print() function, so the string is terminated with '\n'

I wrote the following program with aioserial, but I am not receiving anything back:

import aioserial
import asyncio

async def read_and_print(aioserial_instance: aioserial.AioSerial):
    cmd = 'g\n'
    cmd = bytes(cmd.encode())
    print('send', await aioserial_instance.write_async(cmd))
    await asyncio.sleep(2)

    print(('received: ', await aioserial_instance.readline_async()).decode(errors='ignore'), end='', flush=True)

asyncio.run(read_and_print(aioserial.AioSerial(port='/dev/ttyACM0')))

I've tried this with and without the sleep, and the behavior is the same.

I see that I sent 2 bytes based on the first print statement, but I never received anything back.
I also tried using read_async and the same result.

I am guessing my code contains some dumb mistake. Could you please tell me what I did wrong?
Thanks.

Reading from Two Serial Ports Example

Hey there,

Thank you for creating this library I used this in an installation last year and it worked great!

Since then I am trying to rebuild the project and downloaded the updates you have made since then with pip3 install.

I followed your example given here, however maybe there have been changes which now makes this slightly out of date?

When running the code I now get an issue There is no current event loop in thread 'Thread-1

I wondered how I might adjust the example to work with the loop system?

many thanks in advance!

Getting "coroutine was expected" error while running example

Like so:

Traceback (most recent call last):
  File "discover.py", line 18, in <module>
    asyncio.run(asyncio.gather(read_and_print(aioserial_instance), aioserial_instance.write_async(b'Hello, World!\n')))
  File "/Users/up/anaconda3/envs/winch/lib/python3.8/asyncio/runners.py", line 37, in run
    raise ValueError("a coroutine was expected, got {!r}".format(main))
ValueError: a coroutine was expected, got <_GatheringFuture pending>

Python 3.8

Communication with arduino sometime error

Hi, I've been testing arduino communication with serial.
during running it always connected , buy sometime the read is incorrect
python in run in version 3.9 in Debian 11

This is the python code

import serial
import aioserial
import asyncio
import time
import sys
import logging

class SerialListener:
    def __init__(self, port="/dev/ttyUSB0", baudrate=9600):
        self._port = port
        self._baud_rate = baudrate
        self._run = True
        self._serial = None
        self._logger = logging.getLogger()

    async def stop(self):
        self._run = False

    async def start(self):
        await self.reconnect()
        await self.listen()

    async def disconnect(self):
        self._logger.info("Cleanup serial {}".format(self._port))
        if self._serial is not None:
            try:
                self._serial.close()
            except:
                self._logger.info(sys.exc_info())
        self._serial = None

    async def reconnect(self):
        await self.disconnect()

        while self._serial is None:
            try:
                self._serial = aioserial.AioSerial(
                    port=self._port, baudrate=self._baud_rate
                )
                self._logger.info("Connected to {}".format(self._port))
            except serial.serialutil.SerialException:
                self._logger.info("{} not ready".format(self._port))
            except:
                self._logger.info(sys.exc_info())
            await asyncio.sleep(1)

    async def listen(self):
        while self._run:
            try:
                data = await self._serial.read_until_async()
                code = data.decode(errors="ignore").strip()
                self._logger.info("Receive {}".format(code))
            except asyncio.CancelledError:
                break
            except serial.serialutil.SerialException:
                # Blocking call
                await self.reconnect()
            except:
                self._logger.info(sys.exc_info())
            await asyncio.sleep(0.1)


def main():
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(asctime)s %(levelname)s %(name)s : %(message)s",
        handlers=[logging.StreamHandler(sys.stdout)],
    )
    logger = logging.getLogger()

    serial_listener = SerialListener(port="/dev/ttyUSB0", baudrate=9600)

    loop = asyncio.get_event_loop()

    try:
        asyncio.ensure_future(serial_listener.start())
        loop.run_forever()
    except asyncio.CancelledError:
        logger.info("Receive Cancel")
    except KeyboardInterrupt:
        logger.info("Receive Keyboard Interrupt")
    except:
        logger.info("Unknown Error")
        logger.info(sys.exc_info())
    finally:
        logger.info("Stop application")

    loop.run_until_complete(asyncio.wait([serial_listener.stop()]))

if __name__ == "__main__":
    main()

This is arduino part

const int buttonPin = 4;

int buttonState = 0;
int value = 0;

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  buttonState = digitalRead(4);

  if (buttonState == HIGH) 
  {
    value = 0x01;
  } else if (buttonState == LOW) 
  {
    value = 0x00;
  }

  if (Serial.available()) {
    // Sending value to python
    Serial.println(value);
  }

  delay(500);
}

It is very simple code, basically the arduino will send a value 0x01 when button is press and 0x00 when not every .5 second

When starting the python script, sometime the delay is lower then .5 second, and any button press in the arduino will not send the correct value , it kept sending the default value.

On re-connection either by repluging the usb cable or restarting the script might cause the same problem. The most visible is when I clear the terminal.
Testing same script in windows 10 with port COM3 ( using pyserial ) the problem doesn't seem to occur

Any idea what the cause ?

Example in README.MD does not work

Example in README, aioserial equivalence chapter, does not work.

Here is the traceback:

...test_aiserial.py:16: DeprecationWarning: There is no current event loop
  asyncio.run(asyncio.gather(read_and_print(aioserial_instance), aioserial_instance.write_async(b"Hello, World!\n")))
Traceback (most recent call last):
  File "...test_aiserial.py", line 16, in <module>
    asyncio.run(asyncio.gather(read_and_print(aioserial_instance), aioserial_instance.write_async(b"Hello, World!\n")))
  File "Python311\Lib\asyncio\runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "Python311\Lib\asyncio\runners.py", line 89, in run
    raise ValueError("a coroutine was expected, got {!r}".format(coro))
ValueError: a coroutine was expected, got <_GatheringFuture pending>
sys:1: RuntimeWarning: coroutine 'AioSerial.write_async' was never awaited
sys:1: RuntimeWarning: coroutine 'read_and_print' was never awaited

VSCode Pylance static analysis reports:

Argument of type "Future[tuple[None, int]]" cannot be assigned to parameter "main" of type "Coroutine[Any, Any, _T@run]" in function "run"
  "Future[tuple[None, int]]" is incompatible with "Coroutine[Any, Any, _T@run]"Pylance[reportGeneralTypeIssues](https://github.com/microsoft/pyright/blob/main/docs/configuration.md#reportGeneralTypeIssues)

Windows 10, 64 bit
Python 3.11.0
aioserial==1.3.1
pyserial==3.5

Serial data missed/skipped

Hello,

I'm unsure how to make this repeatable for others without a similar hardware device, but I am experiencing a lot of data loss.

I have a USB serial device that writes out a bytecode. I am reading with await aioserial_instance.read_async(size=1). Every once in a while (maybe when it switches between async tasks), the next read byte has skipped forward quite a bit in the buffer. This is at a really high baudrate (230400).

recv data is not correct

Hi~

when i send follow many times (in hex, no \r\n):

01 01 00 00 00 00 

recv are:

b'\x07\x07\x83\x06\x06\x86'
b'IHII\xc9\xc9'
b'%%\x01!!%'

and the recv code is:

res = await self.ins.read_async(size=AckMsg.ack_size())
print("read data ok!", res)

why?

get_running_loop is only available in python 3.7

Hi,

The version check for the availability of get_running_loop fails for version 3.6.7

>>> sys.version_info
sys.version_info(major=3, minor=6, micro=7, releaselevel='final', serial=0)
>>> sys.version_info > (3, 6)
True
>>> asyncio.get_running_loop
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'asyncio' has no attribute 'get_running_loop'

https://github.com/changyuheng/aioserial/blob/ee2fc0e246c7e815c928e1d2d695f692e5ef9e9b/src/aioserial/aioserial.py#L56
https://github.com/changyuheng/aioserial/blob/ee2fc0e246c7e815c928e1d2d695f692e5ef9e9b/src/aioserial/aioserial.py#L57

asyncio.get_running_loop is new in version 3.7

Regards,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.