wireio 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ - package-ecosystem: "github-actions"
8
+ directory: "/"
9
+ schedule:
10
+ interval: "weekly"
@@ -0,0 +1,41 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ matrix:
14
+ os: [ubuntu-latest, macos-latest]
15
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
16
+ fail-fast: false
17
+ steps:
18
+ - uses: actions/checkout@v6
19
+ - uses: actions/setup-python@v6
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v7
24
+ - name: Install dependencies
25
+ run: uv sync
26
+ - name: Run tests
27
+ run: uv run pytest tests/ -v
28
+
29
+ typecheck:
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v6
33
+ - uses: actions/setup-python@v6
34
+ with:
35
+ python-version: "3.13"
36
+ - name: Install uv
37
+ uses: astral-sh/setup-uv@v7
38
+ - name: Install dependencies
39
+ run: uv sync
40
+ - name: Run mypy
41
+ run: uv run mypy src/ --strict
@@ -0,0 +1,20 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v6
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v7
17
+ - name: Build distribution
18
+ run: uv build
19
+ - name: Publish to PyPI
20
+ run: uv publish
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ .venv/
5
+ .env
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .mypy_cache/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ coverage/
13
+ htmlcov/
14
+ .coverage
15
+ uv.lock
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.0] — 2026-03-14
8
+
9
+ ### Added
10
+ - `Serial` class with synchronous serial port I/O (POSIX backend via termios/fcntl)
11
+ - `AsyncSerial` class wrapping `Serial` with asyncio (`run_in_executor`)
12
+ - `SerialConfig` dataclass for structured port configuration
13
+ - Enums: `Parity`, `StopBits`, `ByteSize`, `FlowControl`
14
+ - Exception hierarchy: `SerialError`, `PortNotFoundError`, `ConfigError`, `SerialTimeoutError`
15
+ - Buffered read helpers: `read_until(delimiter)`, `read_line()`, `read_exactly(size)`
16
+ - `list_ports()` for platform-specific serial port enumeration (Linux sysfs, macOS IOKit, Windows SetupAPI)
17
+ - Windows backend via ctypes (`Win32Serial`)
18
+ - Context manager support: `with Serial(...) as port:` and `async with AsyncSerial(...) as port:`
19
+ - `wireio-list-ports` CLI tool to enumerate serial devices
20
+ - `wireio-miniterm` interactive terminal CLI with `--encoding`, `--echo`, `--eol` options
21
+ - Full PEP 561 type annotations (`py.typed` marker)
22
+ - Python 3.10–3.13+ support with zero dependencies
wireio-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agentine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
wireio-0.1.0/Makefile ADDED
@@ -0,0 +1,22 @@
1
+ .PHONY: install build test lint fmt clean ci
2
+
3
+ install:
4
+ uv sync
5
+
6
+ build:
7
+ uv build
8
+
9
+ test:
10
+ uv run pytest tests/ -v
11
+
12
+ lint:
13
+ uv run ruff check src/
14
+ uv run mypy src/
15
+
16
+ fmt:
17
+ uv run ruff format src/ tests/
18
+
19
+ clean:
20
+ rm -rf dist/ build/ *.egg-info/ .pytest_cache/ .mypy_cache/
21
+
22
+ ci: lint test
wireio-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,316 @@
1
+ Metadata-Version: 2.4
2
+ Name: wireio
3
+ Version: 0.1.0
4
+ Summary: Modern Python serial port library — pyserial replacement
5
+ Project-URL: Homepage, https://github.com/agentine/wireio
6
+ Project-URL: Repository, https://github.com/agentine/wireio
7
+ Author: Agentine
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: com,embedded,hardware,iot,rs232,serial,tty,uart
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Communications
20
+ Classifier: Topic :: System :: Hardware
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+
25
+ # wireio
26
+
27
+ [![PyPI](https://img.shields.io/pypi/v/wireio)](https://pypi.org/project/wireio/)
28
+ [![Python](https://img.shields.io/pypi/pyversions/wireio)](https://pypi.org/project/wireio/)
29
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
30
+
31
+ Modern Python serial port library — a drop-in replacement for [pyserial](https://github.com/pyserial/pyserial).
32
+
33
+ - **Python 3.10+** with full type annotations (PEP 561)
34
+ - **Zero dependencies** — stdlib only
35
+ - **Cross-platform** — Linux, macOS, Windows
36
+ - **Built-in async** — `AsyncSerial` with native asyncio support
37
+ - **Port discovery** — `list_ports()` enumerates available serial devices
38
+ - **CLI tools** — `wireio-miniterm` and `wireio-list-ports`
39
+
40
+ ## Why wireio?
41
+
42
+ [pyserial](https://github.com/pyserial/pyserial) is used by 100,000+ projects but has had no release since November 2020. The single maintainer is unreachable, 275+ issues are open, and the library has no type hints, no async support, and still targets Python 2.7.
43
+
44
+ wireio is a modern replacement:
45
+
46
+ | | pyserial | wireio |
47
+ |---|---|---|
48
+ | Last release | Nov 2020 | Active |
49
+ | Dependencies | None | **None** |
50
+ | Python support | 2.7, 3.4–3.8 | **3.10+ (including 3.13+)** |
51
+ | Type hints | No | **Full PEP 561** |
52
+ | Async support | Separate package | **Built-in `AsyncSerial`** |
53
+ | API style | Old-style class | **Dataclass config + enums** |
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install wireio
59
+ ```
60
+
61
+ ## Quick Start
62
+
63
+ ### Synchronous
64
+
65
+ ```python
66
+ from wireio import Serial
67
+
68
+ with Serial("/dev/ttyUSB0", baudrate=115200) as port:
69
+ port.write(b"AT\r\n")
70
+ response = port.read_until(b"\r\n")
71
+ print(response)
72
+ ```
73
+
74
+ ### Asynchronous
75
+
76
+ ```python
77
+ import asyncio
78
+ from wireio import AsyncSerial
79
+
80
+ async def main():
81
+ async with AsyncSerial("/dev/ttyUSB0", baudrate=9600) as port:
82
+ await port.write(b"hello")
83
+ data = await port.read(100)
84
+ print(data)
85
+
86
+ asyncio.run(main())
87
+ ```
88
+
89
+ ### Port Discovery
90
+
91
+ ```python
92
+ from wireio import list_ports
93
+
94
+ for port in list_ports():
95
+ print(f"{port.device} — {port.description}")
96
+ ```
97
+
98
+ ### Dataclass Config
99
+
100
+ ```python
101
+ from wireio import Serial, SerialConfig, Parity, StopBits
102
+
103
+ config = SerialConfig(
104
+ baudrate=115200,
105
+ parity=Parity.EVEN,
106
+ stopbits=StopBits.TWO,
107
+ timeout=1.0,
108
+ )
109
+ with Serial("/dev/ttyS0", config=config) as port:
110
+ port.write(b"data")
111
+ ```
112
+
113
+ ## API Reference
114
+
115
+ ### `Serial(port, baudrate=9600, **kwargs)`
116
+
117
+ Main serial port class. Platform-specific backend is selected automatically (POSIX on Linux/macOS, Win32 on Windows).
118
+
119
+ **Constructor parameters:**
120
+ - `port` — device path (`/dev/ttyUSB0`, `COM3`)
121
+ - `baudrate` — baud rate (default: `9600`)
122
+ - `bytesize` — `ByteSize.FIVE` through `ByteSize.EIGHT` (default: `EIGHT`)
123
+ - `parity` — `Parity.NONE`, `EVEN`, `ODD`, `MARK`, `SPACE` (default: `NONE`)
124
+ - `stopbits` — `StopBits.ONE`, `ONE_POINT_FIVE`, `TWO` (default: `ONE`)
125
+ - `timeout` — read timeout in seconds (`None` = blocking, `0` = non-blocking, default: `None`)
126
+ - `write_timeout` — write timeout in seconds (`None` = blocking, default: `None`)
127
+ - `flow_control` — `FlowControl.NONE`, `HARDWARE`, `SOFTWARE` (default: `NONE`)
128
+ - `xonxoff` — enable XON/XOFF software flow control (default: `False`)
129
+ - `rtscts` — enable RTS/CTS hardware flow control (default: `False`)
130
+ - `dsrdtr` — enable DSR/DTR hardware flow control (default: `False`)
131
+ - `inter_byte_timeout` — timeout between consecutive bytes in seconds (`None` = disabled, default: `None`)
132
+ - `config` — `SerialConfig` object (overrides individual params when provided)
133
+
134
+ **Methods:**
135
+ - `open()` / `close()` — open or close the port
136
+ - `read(size=1) -> bytes` — read up to `size` bytes; returns fewer if timeout expires
137
+ - `write(data) -> int` — write bytes, returns count written
138
+ - `flush()` — wait until all data transmitted
139
+ - `read_until(delimiter=b"\n", size=0) -> bytes` — read until delimiter found; `size=0` means no limit
140
+ - `read_line() -> bytes` — read until `\n`
141
+ - `read_exactly(size) -> bytes` — read exactly `size` bytes, blocking until all received
142
+ - `configure(config)` — apply new `SerialConfig` to an open port
143
+
144
+ **Properties:**
145
+ - `is_open` — whether the port is open
146
+ - `in_waiting` — bytes available in input buffer
147
+ - `port`, `baudrate`, `bytesize`, `parity`, `stopbits`, `timeout`, `config`
148
+
149
+ Supports the context manager protocol (`with Serial(...) as port:`).
150
+
151
+ ### `AsyncSerial(port, baudrate=9600, **kwargs)`
152
+
153
+ Async wrapper around `Serial`. Runs blocking I/O in a thread executor via `asyncio.run_in_executor`. Same constructor parameters as `Serial`, plus:
154
+
155
+ - `loop` (`asyncio.AbstractEventLoop | None`) — event loop to use (default: current running loop)
156
+
157
+ All I/O methods are `async` and mirror the `Serial` interface:
158
+
159
+ ```python
160
+ async with AsyncSerial("/dev/ttyUSB0", baudrate=9600) as port:
161
+ await port.write(b"hello")
162
+ data = await port.read(100)
163
+ line = await port.read_line()
164
+ exact = await port.read_exactly(4)
165
+ until = await port.read_until(b"\r\n")
166
+ await port.flush()
167
+ await port.configure(SerialConfig(baudrate=115200))
168
+ ```
169
+
170
+ **Async methods:** `open`, `close`, `read`, `write`, `flush`, `read_until`, `read_line`, `read_exactly`, `configure`
171
+
172
+ **Properties (sync):** `port`, `is_open`, `baudrate`, `config`
173
+
174
+ Supports the async context manager protocol (`async with AsyncSerial(...) as port:`).
175
+
176
+ ### `SerialConfig`
177
+
178
+ Dataclass holding all serial port settings. Pass as the `config=` argument to `Serial` or `AsyncSerial`, or use `port.configure(config)` to reconfigure an open port.
179
+
180
+ ```python
181
+ from wireio import SerialConfig, Parity, StopBits, ByteSize, FlowControl
182
+
183
+ config = SerialConfig(
184
+ baudrate=115200,
185
+ bytesize=ByteSize.EIGHT,
186
+ parity=Parity.NONE,
187
+ stopbits=StopBits.ONE,
188
+ timeout=1.0,
189
+ write_timeout=None,
190
+ flow_control=FlowControl.NONE,
191
+ inter_byte_timeout=None,
192
+ )
193
+ ```
194
+
195
+ **Fields** (all optional, defaults match `Serial` constructor):
196
+ - `baudrate: int` — default `9600`
197
+ - `bytesize: ByteSize` — default `ByteSize.EIGHT`
198
+ - `parity: Parity` — default `Parity.NONE`
199
+ - `stopbits: StopBits` — default `StopBits.ONE`
200
+ - `timeout: float | None` — default `None`
201
+ - `write_timeout: float | None` — default `None`
202
+ - `flow_control: FlowControl` — default `FlowControl.NONE`
203
+ - `xonxoff: bool` — default `False`
204
+ - `rtscts: bool` — default `False`
205
+ - `dsrdtr: bool` — default `False`
206
+ - `inter_byte_timeout: float | None` — default `None`
207
+
208
+ `SerialConfig.validate()` raises `ConfigError` for invalid values (negative baudrate, negative timeouts, etc.).
209
+
210
+ ### `list_ports() -> list[PortInfo]`
211
+
212
+ Enumerate available serial ports. Returns `PortInfo` objects with:
213
+ - `device` — device path (e.g. `/dev/ttyUSB0`, `COM3`)
214
+ - `name` — short port name
215
+ - `description` — human-readable description
216
+ - `hwid` — hardware ID string
217
+ - `vid`, `pid` — USB vendor/product IDs (or `None`)
218
+ - `serial_number`, `manufacturer`, `product` — USB metadata (or `None`)
219
+
220
+ ### Enums
221
+
222
+ - `Parity` — `NONE`, `EVEN`, `ODD`, `MARK`, `SPACE`
223
+ - `StopBits` — `ONE`, `ONE_POINT_FIVE`, `TWO`
224
+ - `ByteSize` — `FIVE`, `SIX`, `SEVEN`, `EIGHT`
225
+ - `FlowControl` — `NONE`, `HARDWARE`, `SOFTWARE`
226
+
227
+ ### Exceptions
228
+
229
+ All exceptions inherit from `SerialError`:
230
+
231
+ - `SerialError` — base exception for all serial port errors
232
+ - `PortNotFoundError` — port not found or permission denied
233
+ - `ConfigError` — invalid configuration value
234
+ - `SerialTimeoutError` — operation timed out
235
+
236
+ ## CLI Tools
237
+
238
+ ### `wireio-list-ports`
239
+
240
+ Print a table of available serial ports:
241
+
242
+ ```bash
243
+ wireio-list-ports
244
+ # or
245
+ python -m wireio.tools.list_ports
246
+ ```
247
+
248
+ Output example:
249
+ ```
250
+ DEVICE DESCRIPTION HWID
251
+ -------------------------------------------------
252
+ /dev/ttyUSB0 USB Serial Device USB VID:PID=0403:6001
253
+ /dev/ttyS0 ttyS0 n/a
254
+ ```
255
+
256
+ ### `wireio-miniterm`
257
+
258
+ Interactive serial terminal:
259
+
260
+ ```bash
261
+ wireio-miniterm /dev/ttyUSB0 115200
262
+ # or
263
+ python -m wireio.tools.miniterm /dev/ttyUSB0 115200 --echo --eol crlf
264
+ ```
265
+
266
+ **Options:**
267
+ - `port` — serial port device path (required)
268
+ - `baudrate` — baud rate (default: `9600`)
269
+ - `--encoding` — character encoding for display and input (default: `utf-8`)
270
+ - `--echo` — enable local echo of typed input
271
+ - `--eol {cr,lf,crlf}` — line ending appended to transmitted lines (default: `crlf`)
272
+
273
+ Press `Ctrl+C` to exit.
274
+
275
+ ## Migration from pyserial
276
+
277
+ wireio is designed as a drop-in replacement for pyserial. Key differences:
278
+
279
+ | pyserial | wireio |
280
+ |---|---|
281
+ | `import serial` | `from wireio import Serial` |
282
+ | `serial.Serial(...)` | `Serial(...)` |
283
+ | `serial.tools.list_ports.comports()` | `wireio.list_ports()` |
284
+ | `serial.SerialException` | `wireio.SerialError` |
285
+ | `serial.serialutil.SerialTimeoutException` | `wireio.SerialTimeoutError` |
286
+ | Separate `pyserial-asyncio` package | Built-in `AsyncSerial` |
287
+ | No type hints | Full PEP 561 type hints |
288
+ | Python 2.7+ | Python 3.10+ |
289
+
290
+ ### Common patterns
291
+
292
+ ```python
293
+ # pyserial
294
+ import serial
295
+ ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
296
+ ser.write(b'hello')
297
+ data = ser.read(100)
298
+ ser.close()
299
+
300
+ # wireio (same pattern works)
301
+ from wireio import Serial
302
+ ser = Serial('/dev/ttyUSB0', 9600, timeout=1)
303
+ ser.open()
304
+ ser.write(b'hello')
305
+ data = ser.read(100)
306
+ ser.close()
307
+
308
+ # wireio (preferred — context manager)
309
+ with Serial('/dev/ttyUSB0', baudrate=9600, timeout=1) as ser:
310
+ ser.write(b'hello')
311
+ data = ser.read(100)
312
+ ```
313
+
314
+ ## License
315
+
316
+ MIT
wireio-0.1.0/PLAN.md ADDED
@@ -0,0 +1,115 @@
1
+ # wireio — Modern Python Serial Port Library
2
+
3
+ **Replaces:** [pyserial](https://github.com/pyserial/pyserial) (13M downloads/month, last release Nov 2020)
4
+ **Package name:** wireio (verified available on PyPI)
5
+ **Language:** Python 3.10+
6
+ **Dependencies:** Zero (stdlib only)
7
+
8
+ ## Why Replace pyserial
9
+
10
+ - **Single maintainer** (Chris Liechti) — no visible activity since 2020
11
+ - **5+ years without a release** — last version 3.5 on Nov 23, 2020
12
+ - **275 open issues, 54 unmerged PRs** — no maintainer responses
13
+ - **Outdated Python support** — still lists Python 2.7 and 3.4-3.8
14
+ - **No type hints** — no PEP 561 compliance
15
+ - **No built-in async** — requires separate `pyserial-asyncio` package
16
+ - **Used by 101K projects** — massive bus factor risk
17
+ - **No active fork** has gained traction
18
+
19
+ ## Scope
20
+
21
+ A modern, type-safe serial port communication library providing:
22
+
23
+ 1. Synchronous and asynchronous serial port I/O
24
+ 2. Cross-platform backends (POSIX via termios, Windows via ctypes/win32)
25
+ 3. Port discovery and enumeration
26
+ 4. Familiar API for easy migration from pyserial
27
+
28
+ ## Architecture
29
+
30
+ ```
31
+ wireio/
32
+ ├── __init__.py # Public API: Serial, list_ports, exceptions, enums
33
+ ├── _types.py # Enums: Parity, StopBits, ByteSize, FlowControl
34
+ ├── _config.py # SerialConfig dataclass
35
+ ├── _base.py # SerialBase abstract base class
36
+ ├── _posix.py # POSIX backend (termios, fcntl)
37
+ ├── _win32.py # Windows backend (ctypes + kernel32/setupapi)
38
+ ├── _exceptions.py # SerialError, PortNotFoundError, ConfigError
39
+ ├── _ports.py # Port discovery (platform-specific enumeration)
40
+ ├── _async.py # AsyncSerial class (asyncio native)
41
+ ├── py.typed # PEP 561 marker
42
+ └── tools/
43
+ ├── __init__.py
44
+ ├── miniterm.py # Interactive terminal emulator
45
+ └── list_ports.py # CLI: python -m wireio.tools.list_ports
46
+ ```
47
+
48
+ ## Key Design Decisions
49
+
50
+ - **Python 3.10+** — use match/case, modern type unions, dataclasses with slots
51
+ - **Zero dependencies** — stdlib only (termios, ctypes, fcntl, asyncio)
52
+ - **Full type hints** — PEP 561 compliant, strict mypy
53
+ - **Dataclass config** — `SerialConfig` instead of scattered constructor params
54
+ - **Enum-based settings** — `Parity.NONE`, `StopBits.ONE`, `ByteSize.EIGHT`
55
+ - **Context manager** — `with Serial("/dev/ttyUSB0") as port:`
56
+ - **Built-in async** — `async with AsyncSerial(...) as port:` (no extra package)
57
+ - **Buffered reads** — `read_until(delimiter)`, `read_line()`, `read_exactly(n)`
58
+
59
+ ## Module Breakdown
60
+
61
+ ### Phase 1: Core (MVP)
62
+ - `_types.py` — Parity, StopBits, ByteSize, FlowControl enums
63
+ - `_config.py` — SerialConfig dataclass (baudrate, bytesize, parity, stopbits, timeout, flow control)
64
+ - `_exceptions.py` — Exception hierarchy
65
+ - `_base.py` — SerialBase ABC (open, close, read, write, configure, properties)
66
+ - `_posix.py` — PosixSerial implementation using termios/fcntl
67
+ - `__init__.py` — Public API, platform auto-detection
68
+
69
+ ### Phase 2: Windows + Port Discovery
70
+ - `_win32.py` — Win32Serial using ctypes (CreateFile, SetCommState, etc.)
71
+ - `_ports.py` — Platform-specific port enumeration (sysfs on Linux, IOKit on macOS, SetupAPI on Windows)
72
+
73
+ ### Phase 3: Async + Tools
74
+ - `_async.py` — AsyncSerial wrapping sync backend with asyncio
75
+ - `tools/miniterm.py` — Interactive terminal
76
+ - `tools/list_ports.py` — CLI tool
77
+
78
+ ### Phase 4: Testing + Polish
79
+ - Virtual serial port tests using PTY pairs (os.openpty)
80
+ - Integration test harness
81
+ - Migration guide from pyserial
82
+ - Documentation
83
+
84
+ ## Public API Surface
85
+
86
+ ```python
87
+ from wireio import Serial, AsyncSerial, list_ports
88
+ from wireio import Parity, StopBits, ByteSize, FlowControl
89
+ from wireio import SerialConfig, SerialError
90
+
91
+ # Sync usage
92
+ with Serial("/dev/ttyUSB0", baudrate=115200) as port:
93
+ port.write(b"AT\r\n")
94
+ response = port.read_until(b"\r\n")
95
+
96
+ # Async usage
97
+ async with AsyncSerial("/dev/ttyUSB0", baudrate=9600) as port:
98
+ await port.write(b"hello")
99
+ data = await port.read(100)
100
+
101
+ # Port discovery
102
+ for port_info in list_ports():
103
+ print(f"{port_info.device} — {port_info.description}")
104
+
105
+ # Dataclass config
106
+ config = SerialConfig(baudrate=115200, parity=Parity.EVEN, stopbits=StopBits.TWO)
107
+ port = Serial("/dev/ttyS0", config=config)
108
+ ```
109
+
110
+ ## Testing Strategy
111
+
112
+ - **Unit tests:** Mock termios/ctypes calls, test config validation, enum behavior
113
+ - **PTY tests:** Use `os.openpty()` to create virtual serial port pairs for read/write testing
114
+ - **Platform tests:** CI on Linux (primary), macOS (secondary); Windows via contributor testing
115
+ - **Compatibility tests:** Verify API parity with pyserial for migration scenarios