azhi-serial 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.
- azhi_serial-0.1.0/PKG-INFO +32 -0
- azhi_serial-0.1.0/README.md +23 -0
- azhi_serial-0.1.0/azhi_serial.egg-info/PKG-INFO +32 -0
- azhi_serial-0.1.0/azhi_serial.egg-info/SOURCES.txt +17 -0
- azhi_serial-0.1.0/azhi_serial.egg-info/dependency_links.txt +1 -0
- azhi_serial-0.1.0/azhi_serial.egg-info/entry_points.txt +2 -0
- azhi_serial-0.1.0/azhi_serial.egg-info/requires.txt +2 -0
- azhi_serial-0.1.0/azhi_serial.egg-info/top_level.txt +3 -0
- azhi_serial-0.1.0/comtool.py +59 -0
- azhi_serial-0.1.0/main.py +9 -0
- azhi_serial-0.1.0/pyproject.toml +24 -0
- azhi_serial-0.1.0/setup.cfg +4 -0
- azhi_serial-0.1.0/util/ChecksumHelper.py +34 -0
- azhi_serial-0.1.0/util/Color.py +13 -0
- azhi_serial-0.1.0/util/ConsoleUI.py +110 -0
- azhi_serial-0.1.0/util/LocalSender.py +62 -0
- azhi_serial-0.1.0/util/SerialManager.py +74 -0
- azhi_serial-0.1.0/util/__init__.py +0 -0
- azhi_serial-0.1.0/util/readline3.py +37 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: azhi-serial
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pyreadline3>=3.5.4
|
|
8
|
+
Requires-Dist: pyserial>=3.5
|
|
9
|
+
|
|
10
|
+
"C:\Users\azhi_\AppData\Roaming\uv\uv.toml"
|
|
11
|
+
```bash
|
|
12
|
+
[[index]]
|
|
13
|
+
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
14
|
+
default = true
|
|
15
|
+
```
|
|
16
|
+
```bash
|
|
17
|
+
uv init
|
|
18
|
+
uv add pyserial
|
|
19
|
+
uv add readline3
|
|
20
|
+
uv run python main.py
|
|
21
|
+
```
|
|
22
|
+
```bash
|
|
23
|
+
uv venv
|
|
24
|
+
uv sync
|
|
25
|
+
uv build
|
|
26
|
+
pip install .\dist\azhi_serial-0.1.0-py3-none-any.whl
|
|
27
|
+
azhi-serial --help
|
|
28
|
+
uv publish
|
|
29
|
+
```
|
|
30
|
+
```bash
|
|
31
|
+
pyinstaller -F -i "E:\ziyuan\banner\z_1997.ico" main.py
|
|
32
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"C:\Users\azhi_\AppData\Roaming\uv\uv.toml"
|
|
2
|
+
```bash
|
|
3
|
+
[[index]]
|
|
4
|
+
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
5
|
+
default = true
|
|
6
|
+
```
|
|
7
|
+
```bash
|
|
8
|
+
uv init
|
|
9
|
+
uv add pyserial
|
|
10
|
+
uv add readline3
|
|
11
|
+
uv run python main.py
|
|
12
|
+
```
|
|
13
|
+
```bash
|
|
14
|
+
uv venv
|
|
15
|
+
uv sync
|
|
16
|
+
uv build
|
|
17
|
+
pip install .\dist\azhi_serial-0.1.0-py3-none-any.whl
|
|
18
|
+
azhi-serial --help
|
|
19
|
+
uv publish
|
|
20
|
+
```
|
|
21
|
+
```bash
|
|
22
|
+
pyinstaller -F -i "E:\ziyuan\banner\z_1997.ico" main.py
|
|
23
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: azhi-serial
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pyreadline3>=3.5.4
|
|
8
|
+
Requires-Dist: pyserial>=3.5
|
|
9
|
+
|
|
10
|
+
"C:\Users\azhi_\AppData\Roaming\uv\uv.toml"
|
|
11
|
+
```bash
|
|
12
|
+
[[index]]
|
|
13
|
+
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
14
|
+
default = true
|
|
15
|
+
```
|
|
16
|
+
```bash
|
|
17
|
+
uv init
|
|
18
|
+
uv add pyserial
|
|
19
|
+
uv add readline3
|
|
20
|
+
uv run python main.py
|
|
21
|
+
```
|
|
22
|
+
```bash
|
|
23
|
+
uv venv
|
|
24
|
+
uv sync
|
|
25
|
+
uv build
|
|
26
|
+
pip install .\dist\azhi_serial-0.1.0-py3-none-any.whl
|
|
27
|
+
azhi-serial --help
|
|
28
|
+
uv publish
|
|
29
|
+
```
|
|
30
|
+
```bash
|
|
31
|
+
pyinstaller -F -i "E:\ziyuan\banner\z_1997.ico" main.py
|
|
32
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
comtool.py
|
|
3
|
+
main.py
|
|
4
|
+
pyproject.toml
|
|
5
|
+
azhi_serial.egg-info/PKG-INFO
|
|
6
|
+
azhi_serial.egg-info/SOURCES.txt
|
|
7
|
+
azhi_serial.egg-info/dependency_links.txt
|
|
8
|
+
azhi_serial.egg-info/entry_points.txt
|
|
9
|
+
azhi_serial.egg-info/requires.txt
|
|
10
|
+
azhi_serial.egg-info/top_level.txt
|
|
11
|
+
util/ChecksumHelper.py
|
|
12
|
+
util/Color.py
|
|
13
|
+
util/ConsoleUI.py
|
|
14
|
+
util/LocalSender.py
|
|
15
|
+
util/SerialManager.py
|
|
16
|
+
util/__init__.py
|
|
17
|
+
util/readline3.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
串口调试工具
|
|
3
|
+
只依赖pyserial,支持自动检测串口、循环发送、HEX模式和校验功能
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
import util.readline3 as readline3
|
|
9
|
+
from util.SerialManager import SerialManager
|
|
10
|
+
from util.LocalSender import LoopSender
|
|
11
|
+
from util.ConsoleUI import ConsoleUI
|
|
12
|
+
from util.Color import cprint, Color
|
|
13
|
+
# -------------------------- 主程序入口 --------------------------
|
|
14
|
+
def main():
|
|
15
|
+
parser = argparse.ArgumentParser(description="串口调试助手 - 优雅的串口调试工具,支持自动检测、循环发送、HEX模式和校验功能")
|
|
16
|
+
parser.add_argument("-P", "--port", type=str, default=None, help="串口名称 (默认自动检测)")
|
|
17
|
+
parser.add_argument("-b", "--baud", type=int, default=9600, help="波特率")
|
|
18
|
+
parser.add_argument("-p", "--parity", choices=["N", "E", "O"], default="N", help="校验位")
|
|
19
|
+
parser.add_argument("--hex", action="store_true", help="HEX模式")
|
|
20
|
+
parser.add_argument("-c", "--choice", type=str, default="n", choices=["n", "s", "m"],
|
|
21
|
+
help="校验方式: n=无, s=Checksum8, m=CRC16")
|
|
22
|
+
parser.add_argument("--loop-period", type=float, default=3.0, help="循环发送间隔时间(秒)")
|
|
23
|
+
parser.add_argument("-t", "--timeout", type=float, default=0.5, help="串口读取超时时间(秒)")
|
|
24
|
+
|
|
25
|
+
args = parser.parse_args()
|
|
26
|
+
|
|
27
|
+
# 1. 初始化串口管理器
|
|
28
|
+
serial_mgr = SerialManager(args.port, args.baud, args.parity, timeout=args.timeout)
|
|
29
|
+
try:
|
|
30
|
+
serial_mgr.open()
|
|
31
|
+
except Exception as e:
|
|
32
|
+
# 启动失败为红色
|
|
33
|
+
cprint(str(e), Color.RED)
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
# 2. 初始化循环发送器
|
|
37
|
+
loop_sender = LoopSender(serial_mgr, loop_period=args.loop_period)
|
|
38
|
+
|
|
39
|
+
# 3. 初始化 UI 并启动
|
|
40
|
+
ui = ConsoleUI(serial_mgr, loop_sender, args.hex, args.choice)
|
|
41
|
+
|
|
42
|
+
print("=" * 50)
|
|
43
|
+
cprint(f"端口: {serial_mgr.port} | 波特: {args.baud} | HEX: {args.hex}", Color.BOLD)
|
|
44
|
+
cprint(f"校验: {args.choice} (n:无, s:CS8+0x16, m:CRC16)", Color.BOLD)
|
|
45
|
+
cprint(f"循环间隔: {args.loop_period}s | 超时: {args.timeout}s", Color.BOLD)
|
|
46
|
+
print("命令: 'loop' 启停循环发送 | Ctrl+C 退出")
|
|
47
|
+
print("=" * 50)
|
|
48
|
+
|
|
49
|
+
ui.start_recv_thread()
|
|
50
|
+
# ui.run_interactive_loop() # 启动交互(单机版)
|
|
51
|
+
|
|
52
|
+
readline3.run_interactive_shell(ui.run_interactive_loop) # 启动交互(使用 readline3)
|
|
53
|
+
# 清理资源
|
|
54
|
+
loop_sender.stop_loop()
|
|
55
|
+
serial_mgr.close()
|
|
56
|
+
cprint("\n程序已退出", Color.GREEN)
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
main()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "azhi-serial"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pyreadline3>=3.5.4",
|
|
9
|
+
"pyserial>=3.5",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
# 新增: 定义命令行脚本入口
|
|
13
|
+
[project.scripts]
|
|
14
|
+
azhi-serial = "main:main"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["setuptools>=61.0"]
|
|
18
|
+
build-backend = "setuptools.build_meta"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools]
|
|
21
|
+
# 明确指定顶层模块
|
|
22
|
+
py-modules = ["main", "comtool"]
|
|
23
|
+
# 明确指定子包
|
|
24
|
+
packages = ["util"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -------------------------- 工具类:校验和 helper --------------------------
|
|
2
|
+
class ChecksumHelper:
|
|
3
|
+
@staticmethod
|
|
4
|
+
def modbus_crc16(data: bytes) -> int:
|
|
5
|
+
crc = 0xFFFF
|
|
6
|
+
for b in data:
|
|
7
|
+
crc ^= b
|
|
8
|
+
for _ in range(8):
|
|
9
|
+
if crc & 1:
|
|
10
|
+
crc = (crc >> 1) ^ 0xA001
|
|
11
|
+
else:
|
|
12
|
+
crc >>= 1
|
|
13
|
+
return crc
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def checksum8(data: bytes) -> int:
|
|
17
|
+
return sum(data) & 0xFF
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def apply_checksum(tx_buf: bytes, mode: str) -> bytes:
|
|
21
|
+
"""
|
|
22
|
+
根据模式追加校验和
|
|
23
|
+
mode: 'n' (无), 's' (Checksum8 + 0x16), 'm' (Modbus CRC16)
|
|
24
|
+
"""
|
|
25
|
+
if mode == 's':
|
|
26
|
+
cs = ChecksumHelper.checksum8(tx_buf)
|
|
27
|
+
return tx_buf + bytes([cs, 0x16])
|
|
28
|
+
elif mode == 'm':
|
|
29
|
+
crc = ChecksumHelper.modbus_crc16(tx_buf)
|
|
30
|
+
low = crc & 0xFF
|
|
31
|
+
high = (crc >> 8) & 0xFF
|
|
32
|
+
return tx_buf + bytes([low, high])
|
|
33
|
+
else:
|
|
34
|
+
return tx_buf
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -------------------------- 颜色与工具类 --------------------------
|
|
2
|
+
class Color:
|
|
3
|
+
"""ANSI 颜色代码"""
|
|
4
|
+
RED = '\033[91m'
|
|
5
|
+
GREEN = '\033[92m'
|
|
6
|
+
BLUE = '\033[94m'
|
|
7
|
+
YELLOW = '\033[93m'
|
|
8
|
+
RESET = '\033[0m'
|
|
9
|
+
BOLD = '\033[1m'
|
|
10
|
+
|
|
11
|
+
def cprint(text: str, color: str = Color.RESET):
|
|
12
|
+
"""彩色打印辅助函数"""
|
|
13
|
+
print(f"{color}{text}{Color.RESET}")
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# -------------------------- UI 类:控制台交互 --------------------------
|
|
2
|
+
import threading
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from util.Color import cprint, Color
|
|
6
|
+
from util.ChecksumHelper import ChecksumHelper
|
|
7
|
+
from util.SerialManager import SerialManager
|
|
8
|
+
from util.LocalSender import LoopSender
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConsoleUI:
|
|
12
|
+
def __init__(self, serial_mgr: SerialManager, loop_sender: LoopSender, hex_mode: bool, checksum_mode: str):
|
|
13
|
+
self.serial_mgr = serial_mgr
|
|
14
|
+
self.loop_sender = loop_sender
|
|
15
|
+
self.hex_mode = hex_mode
|
|
16
|
+
self.checksum_mode = checksum_mode.lower()
|
|
17
|
+
self.console_lock = threading.Lock()
|
|
18
|
+
|
|
19
|
+
def start_recv_thread(self):
|
|
20
|
+
t = threading.Thread(target=self._recv_loop, daemon=True)
|
|
21
|
+
t.start()
|
|
22
|
+
|
|
23
|
+
def _recv_loop(self):
|
|
24
|
+
while self.serial_mgr.is_open:
|
|
25
|
+
try:
|
|
26
|
+
data = self.serial_mgr.read(1024)
|
|
27
|
+
if not data:
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
with self.console_lock:
|
|
31
|
+
# sys.stdout.write('\r\033[K') # 清除当前行
|
|
32
|
+
|
|
33
|
+
if self.hex_mode:
|
|
34
|
+
msg = f"[RX HEX] {self._bytes2hex(data)}"
|
|
35
|
+
else:
|
|
36
|
+
try:
|
|
37
|
+
msg = f"[RX TXT] {data.decode('utf-8')}"
|
|
38
|
+
except UnicodeDecodeError:
|
|
39
|
+
msg = f"[RX HEX] {self._bytes2hex(data)}"
|
|
40
|
+
|
|
41
|
+
# RX 均为绿色
|
|
42
|
+
sys.stdout.write(f"{Color.GREEN}{msg}{Color.RESET}\n")
|
|
43
|
+
# sys.stdout.write(">> ")
|
|
44
|
+
sys.stdout.flush()
|
|
45
|
+
except Exception:
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
def run_interactive_loop(self):
|
|
49
|
+
try:
|
|
50
|
+
while True:
|
|
51
|
+
try:
|
|
52
|
+
time.sleep(self.serial_mgr.timeout+0.2) # 避免CPU双线程竞争,input()会阻塞线程,自带\r,清除打印行
|
|
53
|
+
user_text = input(">> ")
|
|
54
|
+
except EOFError:
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
if not user_text.strip():
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
# 命令处理
|
|
61
|
+
if user_text.strip().lower() == 'loop':
|
|
62
|
+
if self.loop_sender.loop_flag:
|
|
63
|
+
self.loop_sender.stop_loop()
|
|
64
|
+
else:
|
|
65
|
+
self.loop_sender.start_loop()
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# 数据发送处理
|
|
69
|
+
self._handle_send(user_text)
|
|
70
|
+
|
|
71
|
+
except KeyboardInterrupt:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def _handle_send(self, user_text: str):
|
|
75
|
+
try:
|
|
76
|
+
if self.hex_mode:
|
|
77
|
+
tx_buf = bytes.fromhex(user_text.strip().replace(" ", ""))
|
|
78
|
+
else:
|
|
79
|
+
tx_buf = user_text.encode("utf-8")
|
|
80
|
+
except Exception as e:
|
|
81
|
+
# 错误为红色
|
|
82
|
+
cprint(f"[错误] 输入格式无效: {e}", Color.RED)
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
# 应用校验
|
|
86
|
+
final_buf = ChecksumHelper.apply_checksum(tx_buf, self.checksum_mode)
|
|
87
|
+
|
|
88
|
+
# 打印校验信息(Info 为绿色)
|
|
89
|
+
if self.checksum_mode == 's':
|
|
90
|
+
cs = ChecksumHelper.checksum8(tx_buf)
|
|
91
|
+
cprint(f"[Info] 自动追加 Checksum8: {cs:02X} 16", Color.GREEN)
|
|
92
|
+
elif self.checksum_mode == 'm':
|
|
93
|
+
crc = ChecksumHelper.modbus_crc16(tx_buf)
|
|
94
|
+
cprint(f"[Info] 自动追加 Modbus CRC16: {crc & 0xFF:02X} {(crc >> 8) & 0xFF:02X}", Color.GREEN)
|
|
95
|
+
|
|
96
|
+
# 发送
|
|
97
|
+
try:
|
|
98
|
+
self.serial_mgr.write(final_buf)
|
|
99
|
+
with self.console_lock:
|
|
100
|
+
# TX 为蓝色
|
|
101
|
+
cprint(f"[TX] {self._bytes2hex(final_buf)}", Color.BLUE)
|
|
102
|
+
|
|
103
|
+
self.loop_sender.update_last_data(final_buf)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
# 发送错误为红色
|
|
106
|
+
cprint(f"[发送错误] {e}", Color.RED)
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _bytes2hex(data: bytes) -> str:
|
|
110
|
+
return " ".join(f"{b:02X}" for b in data)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# -------------------------- 业务类:循环发送 --------------------------
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from util.Color import cprint, Color
|
|
6
|
+
from util.SerialManager import SerialManager
|
|
7
|
+
class LoopSender:
|
|
8
|
+
def __init__(self, serial_mgr: SerialManager, loop_period: float = 3.0):
|
|
9
|
+
self.serial_mgr = serial_mgr
|
|
10
|
+
self.loop_period = loop_period
|
|
11
|
+
self.loop_flag = False
|
|
12
|
+
self.last_tx_data = b""
|
|
13
|
+
self.thread: Optional[threading.Thread] = None
|
|
14
|
+
self._lock = threading.Lock()
|
|
15
|
+
|
|
16
|
+
def start_loop(self):
|
|
17
|
+
with self._lock:
|
|
18
|
+
if not self.last_tx_data:
|
|
19
|
+
cprint("[警告] 暂无发送记录,无法开启循环发送", Color.RED)
|
|
20
|
+
return
|
|
21
|
+
if self.loop_flag:
|
|
22
|
+
cprint("[提示] 循环发送已在运行中", Color.GREEN)
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
self.loop_flag = True
|
|
26
|
+
self.thread = threading.Thread(target=self._worker, daemon=True)
|
|
27
|
+
self.thread.start()
|
|
28
|
+
cprint(f"[状态] 已开启{self.loop_period}秒循环发送", Color.GREEN)
|
|
29
|
+
|
|
30
|
+
def stop_loop(self):
|
|
31
|
+
with self._lock:
|
|
32
|
+
if self.loop_flag:
|
|
33
|
+
self.loop_flag = False
|
|
34
|
+
if self.thread:
|
|
35
|
+
self.thread.join(timeout=1)
|
|
36
|
+
cprint("[状态] 已停止循环发送", Color.GREEN)
|
|
37
|
+
else:
|
|
38
|
+
cprint("[提示] 循环发送未运行", Color.GREEN)
|
|
39
|
+
|
|
40
|
+
def _worker(self):
|
|
41
|
+
while True:
|
|
42
|
+
with self._lock:
|
|
43
|
+
if not self.loop_flag or not self.serial_mgr.is_open:
|
|
44
|
+
break
|
|
45
|
+
data_to_send = self.last_tx_data
|
|
46
|
+
|
|
47
|
+
if data_to_send:
|
|
48
|
+
try:
|
|
49
|
+
self.serial_mgr.write(data_to_send)
|
|
50
|
+
# TX 均为蓝色
|
|
51
|
+
cprint(f"\n[循环TX] {self._bytes2hex(data_to_send)}", Color.BLUE)
|
|
52
|
+
except Exception:
|
|
53
|
+
break
|
|
54
|
+
time.sleep(self.loop_period)
|
|
55
|
+
|
|
56
|
+
def update_last_data(self, data: bytes):
|
|
57
|
+
with self._lock:
|
|
58
|
+
self.last_tx_data = data
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def _bytes2hex(data: bytes) -> str:
|
|
62
|
+
return " ".join(f"{b:02X}" for b in data)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# -------------------------- 工具类:串口管理 --------------------------
|
|
2
|
+
import serial
|
|
3
|
+
import serial.tools.list_ports
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from util.Color import cprint, Color
|
|
6
|
+
class SerialManager:
|
|
7
|
+
def __init__(self, port: Optional[str], baudrate: int, parity: str, timeout: float = 0.5):
|
|
8
|
+
self.port = port
|
|
9
|
+
self.baudrate = baudrate
|
|
10
|
+
self.parity = parity
|
|
11
|
+
self.timeout = timeout
|
|
12
|
+
self.ser: Optional[serial.Serial] = None
|
|
13
|
+
|
|
14
|
+
def auto_detect_port(self) -> str:
|
|
15
|
+
"""自动检测端口,如果只有一个则直接使用"""
|
|
16
|
+
ports = list(serial.tools.list_ports.comports())
|
|
17
|
+
if not ports:
|
|
18
|
+
raise Exception("未检测到任何可用串口")
|
|
19
|
+
|
|
20
|
+
if len(ports) == 1:
|
|
21
|
+
detected_port = ports[0].device
|
|
22
|
+
cprint(f"[自动检测] 发现唯一串口: {detected_port} ({ports[0].description})", Color.GREEN)
|
|
23
|
+
return detected_port
|
|
24
|
+
|
|
25
|
+
# 多个端口时,列出并建议用户指定,默认返回第一个
|
|
26
|
+
cprint("[警告] 检测到多个串口:", Color.GREEN)
|
|
27
|
+
for p in ports:
|
|
28
|
+
print(f" - {p.device}: {p.description}")
|
|
29
|
+
default_port = ports[0].device
|
|
30
|
+
cprint(f"[提示] 未指定端口,默认使用: {default_port}", Color.GREEN)
|
|
31
|
+
return default_port
|
|
32
|
+
|
|
33
|
+
def open(self):
|
|
34
|
+
if not self.port:
|
|
35
|
+
self.port = self.auto_detect_port()
|
|
36
|
+
|
|
37
|
+
parity_map = {
|
|
38
|
+
"N": serial.PARITY_NONE,
|
|
39
|
+
"E": serial.PARITY_EVEN,
|
|
40
|
+
"O": serial.PARITY_ODD,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
self.ser = serial.Serial(
|
|
45
|
+
port=self.port,
|
|
46
|
+
baudrate=self.baudrate,
|
|
47
|
+
parity=parity_map.get(self.parity, serial.PARITY_NONE),
|
|
48
|
+
stopbits=serial.STOPBITS_ONE,
|
|
49
|
+
bytesize=serial.EIGHTBITS,
|
|
50
|
+
timeout=self.timeout
|
|
51
|
+
)
|
|
52
|
+
cprint(f"[成功] 串口已打开: {self.port} @ {self.baudrate},校验位: {self.parity}", Color.GREEN)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
raise Exception(f"串口打开失败: {e}")
|
|
55
|
+
|
|
56
|
+
def close(self):
|
|
57
|
+
if self.ser and self.ser.is_open:
|
|
58
|
+
self.ser.close()
|
|
59
|
+
cprint("[信息] 串口已关闭", Color.GREEN)
|
|
60
|
+
|
|
61
|
+
def write(self, data: bytes):
|
|
62
|
+
if self.ser and self.ser.is_open:
|
|
63
|
+
self.ser.write(data)
|
|
64
|
+
else:
|
|
65
|
+
raise Exception("串口未打开")
|
|
66
|
+
|
|
67
|
+
def read(self, size: int = 1024) -> bytes:
|
|
68
|
+
if self.ser and self.ser.is_open:
|
|
69
|
+
return self.ser.read(size)
|
|
70
|
+
return b""
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def is_open(self):
|
|
74
|
+
return self.ser.is_open if self.ser else False
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
交互式命令行shell,支持历史记录和Tab补全。
|
|
3
|
+
只依赖pyreadline3库,适用于Python 3.x版本。
|
|
4
|
+
"""
|
|
5
|
+
import readline
|
|
6
|
+
|
|
7
|
+
def callable():
|
|
8
|
+
while True:
|
|
9
|
+
cmd = input(">>")
|
|
10
|
+
if cmd.lower() in ["q", "exit"]:
|
|
11
|
+
break
|
|
12
|
+
print(f"执行:{cmd}")
|
|
13
|
+
def run_interactive_shell(callable, history_file=".his.txt", history_length=500):
|
|
14
|
+
"""
|
|
15
|
+
运行一个交互式命令行 shell,支持历史记录和 Tab 补全。
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
history_file (str): 历史记录文件路径。
|
|
19
|
+
history_length (int): 最大历史记录条数。
|
|
20
|
+
"""
|
|
21
|
+
readline.parse_and_bind("tab: history-search-backward")
|
|
22
|
+
readline.set_history_length(history_length)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
readline.read_history_file(history_file)
|
|
26
|
+
callable()
|
|
27
|
+
except FileNotFoundError:
|
|
28
|
+
pass
|
|
29
|
+
except KeyboardInterrupt:
|
|
30
|
+
print("\n用户已手动中断程序运行。")
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f"发生错误: {e}")
|
|
33
|
+
finally:
|
|
34
|
+
readline.write_history_file(history_file)
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
run_interactive_shell(callable)
|