fix: SerialMate v0.1.1 接收路径修复——时间戳改 link 线程到达时刻打戳、接收日志批量插入+5000行上限+积压折叠、通道配置缓存;自测扩至 selftest_gui 69 项,新增 ISSUE 报告与 _sim_periodic 复现脚本;重打包
This commit is contained in:
+26
-15
@@ -30,11 +30,14 @@ DEFAULTS = dict(bytesize=8, parity='N', stopbits=1)
|
||||
|
||||
|
||||
class SerialLink:
|
||||
"""串口工作线程。回调(后台线程触发):
|
||||
on_status(str) 连接状态变化(已连接/断开/失败/设备拔出)
|
||||
on_frame(fr) 解析模式: 完整 Frame(protocol.Frame)
|
||||
on_raw(data) 透传模式: 原始接收 bytes
|
||||
on_tx(label, data) 发送回显: label=payload 字符串(自动包装)/None(HEX直发)
|
||||
"""串口工作线程。回调(后台线程触发, 第二参数 ts=事件发生的 epoch 秒):
|
||||
on_status(text, ts) 连接状态变化(已连接/断开/失败/设备拔出)
|
||||
on_frame(fr, ts) 解析模式: 完整 Frame(protocol.Frame); ts=本批字节到达时刻
|
||||
on_raw(data, ts) 透传模式: 原始接收 bytes; ts=到达时刻
|
||||
on_tx(label, data, ts) 发送回显: label=payload 字符串(自动包装)/None(HEX直发);
|
||||
ts=写入串口时刻
|
||||
时间戳必须由本模块在读线程打(字节到达时刻), GUI 消费侧再打会随 UI 卡顿
|
||||
成批挤压失真——日志时间就不再是到达时间。
|
||||
"""
|
||||
|
||||
def __init__(self, cfg, on_status):
|
||||
@@ -100,7 +103,7 @@ class SerialLink:
|
||||
|
||||
def open(self, port, baud=DEFAULT_BAUD, **kw):
|
||||
if not HAS_SERIAL:
|
||||
self.on_status('未安装 pyserial: py -m pip install pyserial')
|
||||
self.on_status('未安装 pyserial: py -m pip install pyserial', time.time())
|
||||
return
|
||||
params = {**DEFAULTS, **kw}
|
||||
with self._lock:
|
||||
@@ -123,7 +126,7 @@ class SerialLink:
|
||||
with self._lock:
|
||||
ser = self.ser
|
||||
if ser is None:
|
||||
self.on_status('未连接串口, 命令被丢弃')
|
||||
self.on_status('未连接串口, 命令被丢弃', time.time())
|
||||
return False
|
||||
self.q.put((label, bytes(frame)))
|
||||
return True
|
||||
@@ -148,7 +151,7 @@ class SerialLink:
|
||||
pass
|
||||
self.ser = None
|
||||
self._open_key = None
|
||||
self.on_status('已断开')
|
||||
self.on_status('已断开', time.time())
|
||||
if target:
|
||||
port, baud, dbsize, parity, sbits = target
|
||||
try:
|
||||
@@ -159,12 +162,19 @@ class SerialLink:
|
||||
parity=parities.get(parity, serial.PARITY_NONE),
|
||||
stopbits=stopbits.get(sbits, serial.STOPBITS_ONE),
|
||||
timeout=0.15,
|
||||
write_timeout=1.0) # 写超时: 转换器停滞时 write 会
|
||||
write_timeout=1.0, # 写超时: 转换器停滞时 write 会
|
||||
# 永久阻塞并卡死线程, 必须设上限
|
||||
inter_byte_timeout=0.02) # 字节间超时(Win32
|
||||
# ReadIntervalTimeout): timeout=0.15 是"总超时"
|
||||
# (150ms 内必返回, 不会无限攒批), 周期上报帧下 read
|
||||
# 会 ~150ms 攒 1~2 帧才返回一批; 设 20ms 后按帧粒度
|
||||
# 返回, 帧到达时间戳精确到单帧。20ms 仍大于 1200 波特
|
||||
# 单字节时间(~8.3ms)×2, 不误切帧
|
||||
with self._lock:
|
||||
self._open_key = target
|
||||
self.parser = None # 换口重建解析器(下次接收时惰性建)
|
||||
self.on_status(f'已连接 {port} @ {baud} {dbsize}{parity}{sbits}')
|
||||
self.on_status(f'已连接 {port} @ {baud} {dbsize}{parity}{sbits}',
|
||||
time.time())
|
||||
except Exception as e:
|
||||
self.ser = None
|
||||
with self._lock:
|
||||
@@ -173,7 +183,7 @@ class SerialLink:
|
||||
if 'PermissionError' in repr(e) or '拒绝访问' in et \
|
||||
or 'Access is denied' in et:
|
||||
et += '(串口被占用: 检查串口助手/另一个本工具实例)'
|
||||
self.on_status(f'连接失败: {et}')
|
||||
self.on_status(f'连接失败: {et}', time.time())
|
||||
if self.ser is not None:
|
||||
try:
|
||||
item = None
|
||||
@@ -185,7 +195,7 @@ class SerialLink:
|
||||
label, frame = item
|
||||
self.ser.write(frame)
|
||||
if self.on_tx is not None:
|
||||
self.on_tx(label, frame)
|
||||
self.on_tx(label, frame, time.time())
|
||||
data = self.ser.read(4096)
|
||||
if data:
|
||||
self._dispatch_rx(data)
|
||||
@@ -198,10 +208,11 @@ class SerialLink:
|
||||
self._open_key = None # 必须同步清除, 否则同参数重连被当作已打开而跳过
|
||||
with self._lock:
|
||||
self._target = None
|
||||
self.on_status('串口丢失(设备拔出?), 请重新连接')
|
||||
self.on_status('串口丢失(设备拔出?), 请重新连接', time.time())
|
||||
time.sleep(0.02)
|
||||
|
||||
def _dispatch_rx(self, data):
|
||||
ts = time.time() # 字节到达时刻: 后台线程打, 随帧携带
|
||||
with self._lock:
|
||||
parse = self._parse
|
||||
if parse and self.parser is None: # 惰性建, 带当前 ProtoConfig
|
||||
@@ -210,6 +221,6 @@ class SerialLink:
|
||||
if parse:
|
||||
for fr in self.parser.feed(data):
|
||||
if self.on_frame is not None:
|
||||
self.on_frame(fr)
|
||||
self.on_frame(fr, ts)
|
||||
elif self.on_raw is not None:
|
||||
self.on_raw(data)
|
||||
self.on_raw(data, ts)
|
||||
|
||||
Reference in New Issue
Block a user