Skip to content

Commit 4168352

Browse files
authored
Merge pull request #14 from intercreate/feature/serial
Feature/serial
2 parents 6c1d723 + 971c49f commit 4168352

File tree

4 files changed

+104
-10
lines changed

4 files changed

+104
-10
lines changed

poetry.lock

+24-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ source = "git-tag"
2121
python = ">=3.10, <3.13"
2222
smpclient = "^1.3.1"
2323
typer = {extras = ["all"], version = "^0.9.0"}
24+
readchar = "^4.0.5"
2425

2526

2627
[tool.poetry.group.dev.dependencies]

smpmgr/main.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from smpclient.requests.os_management import ResetWrite
1616
from typing_extensions import Annotated
1717

18-
from smpmgr import image_management, os_management
18+
from smpmgr import image_management, os_management, terminal
1919
from smpmgr.common import (
2020
Options,
2121
TransportDefinition,
@@ -38,6 +38,7 @@
3838
app.add_typer(os_management.app)
3939
app.add_typer(image_management.app)
4040
app.add_typer(intercreate.app)
41+
app.command()(terminal.terminal)
4142

4243

4344
@app.callback(invoke_without_command=True)

smpmgr/terminal.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import asyncio
2+
import logging
3+
from typing import Final, cast
4+
5+
import readchar
6+
import typer
7+
from serial import Serial
8+
9+
from smpmgr.common import Options
10+
11+
logger = logging.getLogger(__name__)
12+
13+
MAP_KEY_TO_BYTES: Final = {
14+
readchar.key.CTRL_C: b"\x03",
15+
readchar.key.UP: b"\x1b[A",
16+
readchar.key.DOWN: b"\x1b[B",
17+
readchar.key.LEFT: b"\x1b[D",
18+
readchar.key.RIGHT: b"\x1b[C",
19+
readchar.key.ESC: b"\x1b",
20+
}
21+
22+
23+
def terminal(ctx: typer.Context) -> None:
24+
"""Open a terminal to the device."""
25+
26+
options = cast(Options, ctx.obj)
27+
28+
async def f() -> None:
29+
if options.transport.port is None:
30+
print("--port <port> option is required for the terminal, e.g.")
31+
print("smpmgr --port COM1 terminal")
32+
return
33+
34+
print(f"\x1b[2mOpening terminal to {options.transport.port}...", end="")
35+
36+
with Serial(port=options.transport.port, baudrate=115200, timeout=options.timeout) as s:
37+
print("OK")
38+
print("Press Ctrl-T to exit the terminal.\x1b[22m")
39+
print()
40+
device_result, keyboard_result = await asyncio.wait(
41+
(
42+
asyncio.create_task(_rx_from_device(s)),
43+
asyncio.create_task(asyncio.to_thread(_tx_keyboard_to_device, s)),
44+
),
45+
return_when=asyncio.FIRST_EXCEPTION,
46+
)
47+
48+
logger.debug(f"{device_result=}, {keyboard_result=}")
49+
50+
asyncio.run(f())
51+
52+
53+
async def _rx_from_device(port: Serial) -> None:
54+
"""Async poll of the serial port for incoming data.
55+
56+
Can be replaced when pyserial is replaced."""
57+
58+
while True:
59+
_bytes = port.read_all()
60+
if _bytes is not None:
61+
print(_bytes.decode(), end="", flush=True)
62+
await asyncio.sleep(0.020)
63+
64+
65+
def _tx_keyboard_to_device(port: Serial) -> None:
66+
"""Blocking read of keyboard input."""
67+
while True:
68+
try:
69+
key = readchar.readkey()
70+
except KeyboardInterrupt:
71+
key = readchar.key.CTRL_C
72+
if key == readchar.key.CTRL_T:
73+
raise KeyboardInterrupt
74+
try:
75+
port.write(MAP_KEY_TO_BYTES[key])
76+
except KeyError:
77+
port.write(key.encode())

0 commit comments

Comments
 (0)