Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 299c5c1

Browse files
authored
Merge pull request #389 from jumpstarter-dev/pyserial
Implement async pyserial driver
2 parents 30f786f + 2f1194e commit 299c5c1

3 files changed

Lines changed: 46 additions & 14 deletions

File tree

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
11
from contextlib import asynccontextmanager
22
from dataclasses import dataclass, field
33

4+
from anyio import (
5+
create_memory_object_stream,
6+
)
7+
from anyio._backends._asyncio import StreamReaderWrapper, StreamWriterWrapper
48
from anyio.abc import ObjectStream
5-
from anyio.to_thread import run_sync
6-
from serial import Serial, serial_for_url
9+
from anyio.streams.stapled import StapledObjectStream
10+
from serial import serial_for_url
11+
from serial_asyncio import open_serial_connection
712

813
from jumpstarter.driver import Driver, exportstream
914

15+
LOOP = "loop://"
16+
1017

1118
@dataclass(kw_only=True)
1219
class AsyncSerial(ObjectStream):
13-
device: Serial
20+
reader: StreamReaderWrapper
21+
writer: StreamWriterWrapper
1422

1523
async def send(self, item):
16-
await run_sync(self.device.write, item)
24+
await self.writer.send(item)
1725

1826
async def receive(self):
19-
size = max(self.device.in_waiting, 1)
20-
return await run_sync(self.device.read, size)
27+
return await self.reader.receive()
2128

2229
async def send_eof(self):
23-
await run_sync(self.device.close)
30+
pass
2431

2532
async def aclose(self):
26-
await run_sync(self.device.close)
33+
await self.writer.aclose()
34+
await self.reader.aclose()
2735

2836

2937
@dataclass(kw_only=True)
@@ -35,7 +43,7 @@ class PySerial(Driver):
3543
def __post_init__(self):
3644
if hasattr(super(), "__post_init__"):
3745
super().__post_init__()
38-
if self.check_present:
46+
if self.check_present and self.url != LOOP:
3947
serial_for_url(self.url, baudrate=self.baudrate)
4048

4149
@classmethod
@@ -46,7 +54,16 @@ def client(cls) -> str:
4654
@asynccontextmanager
4755
async def connect(self):
4856
self.logger.info("Connecting to %s, baudrate: %d", self.url, self.baudrate)
49-
device = await run_sync(serial_for_url, self.url, self.baudrate)
50-
async with AsyncSerial(device=device) as stream:
51-
yield stream
52-
self.logger.info("Disconnected from %s", self.url)
57+
if self.url != LOOP:
58+
reader, writer = await open_serial_connection(url=self.url, baudrate=self.baudrate, limit=1)
59+
writer.transport.set_write_buffer_limits(high=4096, low=0)
60+
async with AsyncSerial(
61+
reader=StreamReaderWrapper(reader),
62+
writer=StreamWriterWrapper(writer),
63+
) as stream:
64+
yield stream
65+
self.logger.info("Disconnected from %s", self.url)
66+
else:
67+
tx, rx = create_memory_object_stream[bytes](32)
68+
async with StapledObjectStream(tx, rx) as stream:
69+
yield stream

packages/jumpstarter-driver-pyserial/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ dependencies = [
1313
"jumpstarter",
1414
"jumpstarter-driver-network",
1515
"pyserial>=3.5",
16-
"asyncclick>=8.1.7.2"
16+
"asyncclick>=8.1.7.2",
17+
"pyserial-asyncio>=0.6",
1718
]
1819

1920
[dependency-groups]

uv.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)