|
|
@@ -80,6 +80,11 @@ class SerialPort:
|
|
|
timeout=item.get('timeout', 2.0),
|
|
|
min_response_bytes=item.get('min_response_bytes', 1)
|
|
|
)
|
|
|
+ elif cmd_type == 'wait_response':
|
|
|
+ result = self._do_wait_response(
|
|
|
+ timeout=item.get('timeout', 2.0),
|
|
|
+ min_response_bytes=item.get('min_response_bytes', 1)
|
|
|
+ )
|
|
|
elif cmd_type == 'send_raw':
|
|
|
result = self._do_send_raw(item['data'])
|
|
|
elif cmd_type == 'send_data':
|
|
|
@@ -108,7 +113,7 @@ class SerialPort:
|
|
|
return False, "serial port not connected"
|
|
|
bytes_sent = self.ser.write(data)
|
|
|
self.ser.flush()
|
|
|
- if self.send_callback:
|
|
|
+ if self.send_callback and data:
|
|
|
self.send_callback(data.hex())
|
|
|
return True, "send ok"
|
|
|
except Exception as e:
|
|
|
@@ -181,7 +186,7 @@ class SerialPort:
|
|
|
# 485 半双工:等最后字节发完并给收发器一点切换方向的时间
|
|
|
time.sleep(0.03)
|
|
|
|
|
|
- if self.send_callback:
|
|
|
+ if self.send_callback and data:
|
|
|
self.send_callback(data.hex())
|
|
|
|
|
|
# 阶段 2:等待响应。由于后台读线程已暂停,这里独占串口读取。
|
|
|
@@ -227,6 +232,59 @@ class SerialPort:
|
|
|
with self._sync_lock:
|
|
|
self._sync_receive_active = False
|
|
|
|
|
|
+ def _do_wait_response(self, timeout: float = 2.0, min_response_bytes: int = 1) -> bytes:
|
|
|
+ """实际执行 wait_response(在队列工作线程中调用)。
|
|
|
+
|
|
|
+ 与 _do_send_and_wait 的区别:
|
|
|
+ - 不发送任何数据(不 write),因此不触发 send_callback,不会在前端产生空 JSON;
|
|
|
+ - 不清空接收缓冲(不 reset_input_buffer),避免把从机已回的应答冲掉。
|
|
|
+
|
|
|
+ 用于发送完指令(send_raw)后专门等待从机应答。
|
|
|
+ 收到 min_response_bytes 字节后,继续等到总线空闲(100ms 无新数据)再返回,
|
|
|
+ 以便把回显与真实应答一并收齐后再交由调用方解析。
|
|
|
+ """
|
|
|
+ # 进入同步接收模式,暂停后台读取线程,独占串口读取
|
|
|
+ with self._sync_lock:
|
|
|
+ self._sync_receive_active = True
|
|
|
+
|
|
|
+ try:
|
|
|
+ response = b''
|
|
|
+ start = time.time()
|
|
|
+ last_data_time = start
|
|
|
+
|
|
|
+ while time.time() - start < timeout:
|
|
|
+ # 从后台读线程缓冲 + 串口硬件缓冲两处收集
|
|
|
+ chunk = b''
|
|
|
+ with self.lock:
|
|
|
+ while self.raw_response_buffer:
|
|
|
+ try:
|
|
|
+ chunk += bytes.fromhex(self.raw_response_buffer.pop(0))
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+ try:
|
|
|
+ if self.ser and self.ser.is_open and self.ser.in_waiting > 0:
|
|
|
+ chunk += self.ser.read(self.ser.in_waiting)
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+
|
|
|
+ if chunk:
|
|
|
+ response += chunk
|
|
|
+ last_data_time = time.time()
|
|
|
+
|
|
|
+ # 收够最小长度后,等总线空闲再返回,避免回显导致提前返回而漏掉真实应答
|
|
|
+ if len(response) >= min_response_bytes:
|
|
|
+ if time.time() - last_data_time > 0.1:
|
|
|
+ self._emit_received_data(response)
|
|
|
+ return response
|
|
|
+
|
|
|
+ time.sleep(0.01)
|
|
|
+
|
|
|
+ self._emit_received_data(response)
|
|
|
+ return response
|
|
|
+ finally:
|
|
|
+ with self._sync_lock:
|
|
|
+ self._sync_receive_active = False
|
|
|
+
|
|
|
def list_ports(self):
|
|
|
"""列出系统中可用的串口"""
|
|
|
ports = []
|
|
|
@@ -618,3 +676,30 @@ class SerialPort:
|
|
|
return b''
|
|
|
return result_container.get('result', b'')
|
|
|
|
|
|
+ def wait_response(self, timeout: float = 2.0, min_response_bytes: int = 1) -> bytes:
|
|
|
+ """只读取从机应答,不发送任何数据(入队,由队列工作线程统一执行)。
|
|
|
+
|
|
|
+ 与 send_and_wait 的区别:不 write 数据、不触发 send_callback、不清空接收缓冲。
|
|
|
+ 用于发送完指令(send_raw)后等待从机应答,避免发空数据污染前端显示、
|
|
|
+ 避免清缓冲冲掉已到达的应答。
|
|
|
+ """
|
|
|
+ result_container = {}
|
|
|
+ event = threading.Event()
|
|
|
+
|
|
|
+ def callback(result):
|
|
|
+ result_container['result'] = result
|
|
|
+ event.set()
|
|
|
+
|
|
|
+ self._cmd_queue.put({
|
|
|
+ 'type': 'wait_response',
|
|
|
+ 'timeout': timeout,
|
|
|
+ 'min_response_bytes': min_response_bytes,
|
|
|
+ 'callback': callback
|
|
|
+ })
|
|
|
+
|
|
|
+ # 最多等待 timeout + 队列处理余量
|
|
|
+ wait_time = timeout + 5
|
|
|
+ if not event.wait(wait_time):
|
|
|
+ return b''
|
|
|
+ return result_container.get('result', b'')
|
|
|
+
|