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.
@@ -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,2 @@
1
+ [console_scripts]
2
+ azhi-serial = main:main
@@ -0,0 +1,2 @@
1
+ pyreadline3>=3.5.4
2
+ pyserial>=3.5
@@ -0,0 +1,3 @@
1
+ comtool
2
+ main
3
+ util
@@ -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,9 @@
1
+ import comtool
2
+ def main():
3
+ print("Hello from azhi-serial!")
4
+ print("=" * 50)
5
+ comtool.main()
6
+
7
+
8
+ if __name__ == "__main__":
9
+ 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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)