jmri_loconet.transport.serial_port
SerialPort — pyserial-asyncio LocoNet transport.
Wraps a serial_asyncio StreamReader/StreamWriter pair into the
LocoNetPort interface. Used for real LocoBuffer-II / PR3 / PR4
connections.
Default settings match JMRI's defaults: 16,457 baud, 8N1, no flow control.
1"""``SerialPort`` — pyserial-asyncio LocoNet transport. 2 3Wraps a ``serial_asyncio`` ``StreamReader``/``StreamWriter`` pair into the 4:class:`LocoNetPort` interface. Used for real LocoBuffer-II / PR3 / PR4 5connections. 6 7Default settings match JMRI's defaults: 16,457 baud, 8N1, no flow control. 8""" 9from __future__ import annotations 10 11from typing import TYPE_CHECKING 12 13from jmri_loconet.transport.port import LocoNetPort 14 15if TYPE_CHECKING: 16 from asyncio import StreamReader, StreamWriter 17 18# JMRI's LocoBufferII default; lower-end LocoBuffer-I uses 19200, PR3 uses 57600. 19DEFAULT_BAUD = 16457 20 21 22class SerialPort(LocoNetPort): 23 """LocoNet over an async serial port.""" 24 25 def __init__(self, reader: StreamReader, writer: StreamWriter) -> None: 26 self._reader = reader 27 self._writer = writer 28 self._open = True 29 30 @classmethod 31 async def open(cls, port: str, baud: int = DEFAULT_BAUD) -> SerialPort: 32 """Open the serial *port* (e.g. ``"COM5"`` or ``"/dev/loconet"``).""" 33 # Import lazily so the package imports cleanly on systems without 34 # pyserial installed (tests use the loopback transport). 35 import serial_asyncio # noqa: PLC0415 36 reader, writer = await serial_asyncio.open_serial_connection( 37 url=port, baudrate=baud 38 ) 39 return cls(reader=reader, writer=writer) 40 41 async def read_byte(self) -> int: 42 data = await self._reader.readexactly(1) 43 return data[0] 44 45 async def read_exact(self, n: int) -> bytes: 46 return await self._reader.readexactly(n) 47 48 async def write_frame(self, frame: bytes) -> None: 49 if not self._open: 50 raise RuntimeError("port is closed") 51 self._writer.write(frame) 52 await self._writer.drain() 53 54 async def aclose(self) -> None: 55 if not self._open: 56 return 57 self._open = False 58 self._writer.close() 59 import contextlib # noqa: PLC0415 60 with contextlib.suppress(Exception): # best-effort close 61 await self._writer.wait_closed() 62 63 @property 64 def is_open(self) -> bool: 65 return self._open
DEFAULT_BAUD =
16457
23class SerialPort(LocoNetPort): 24 """LocoNet over an async serial port.""" 25 26 def __init__(self, reader: StreamReader, writer: StreamWriter) -> None: 27 self._reader = reader 28 self._writer = writer 29 self._open = True 30 31 @classmethod 32 async def open(cls, port: str, baud: int = DEFAULT_BAUD) -> SerialPort: 33 """Open the serial *port* (e.g. ``"COM5"`` or ``"/dev/loconet"``).""" 34 # Import lazily so the package imports cleanly on systems without 35 # pyserial installed (tests use the loopback transport). 36 import serial_asyncio # noqa: PLC0415 37 reader, writer = await serial_asyncio.open_serial_connection( 38 url=port, baudrate=baud 39 ) 40 return cls(reader=reader, writer=writer) 41 42 async def read_byte(self) -> int: 43 data = await self._reader.readexactly(1) 44 return data[0] 45 46 async def read_exact(self, n: int) -> bytes: 47 return await self._reader.readexactly(n) 48 49 async def write_frame(self, frame: bytes) -> None: 50 if not self._open: 51 raise RuntimeError("port is closed") 52 self._writer.write(frame) 53 await self._writer.drain() 54 55 async def aclose(self) -> None: 56 if not self._open: 57 return 58 self._open = False 59 self._writer.close() 60 import contextlib # noqa: PLC0415 61 with contextlib.suppress(Exception): # best-effort close 62 await self._writer.wait_closed() 63 64 @property 65 def is_open(self) -> bool: 66 return self._open
LocoNet over an async serial port.
31 @classmethod 32 async def open(cls, port: str, baud: int = DEFAULT_BAUD) -> SerialPort: 33 """Open the serial *port* (e.g. ``"COM5"`` or ``"/dev/loconet"``).""" 34 # Import lazily so the package imports cleanly on systems without 35 # pyserial installed (tests use the loopback transport). 36 import serial_asyncio # noqa: PLC0415 37 reader, writer = await serial_asyncio.open_serial_connection( 38 url=port, baudrate=baud 39 ) 40 return cls(reader=reader, writer=writer)
Open the serial port (e.g. "COM5" or "/dev/loconet").
async def
write_frame(self, frame: bytes) -> None:
49 async def write_frame(self, frame: bytes) -> None: 50 if not self._open: 51 raise RuntimeError("port is closed") 52 self._writer.write(frame) 53 await self._writer.drain()
Write a complete LocoNet frame (opcode + payload + checksum).
async def
aclose(self) -> None:
55 async def aclose(self) -> None: 56 if not self._open: 57 return 58 self._open = False 59 self._writer.close() 60 import contextlib # noqa: PLC0415 61 with contextlib.suppress(Exception): # best-effort close 62 await self._writer.wait_closed()
Close the port. Idempotent.