Files
virtual_simulation_midware/midware/drivers/tcp_uart_agent.py
2026-06-16 15:40:19 +08:00

226 lines
8.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from queue import Full
import socket
import threading
import time
from typing import Dict, Optional
from loguru import logger
import telnetlib #2026/6/2
# 全局TCP客户端管理每个UART一个实例
tcp_clients: Dict[str, "TCPUARTClient"] = {}
# HEAD = b"\xeb\x90" #地测帧头
# TAIL = b"\x57\x16" #地测帧尾
# MIN_FRAME = 23 ##地测长度最小值
# MAX_FRAME = 1000 ##地测帧长度最大值
class TCPUARTClient:
def __init__(self, dev_name: str, local_port: int, remote_port: int, remote_ip="127.0.0.1"):
self.dev_name = dev_name
self.local_port = local_port
self.remote_port = remote_port
self.remote_ip = remote_ip
self.socket = None
self.running = False
self.thread = None
self.HEAD = b"\xeb\x90" #地测帧头
self.TAIL = b"\x57\x16" #地测帧尾
self.MIN_LEN = 23 ##地测长度最小值
self.MAX_LEN = 1000 ##地测帧长度最大值
self.tcp_buf = b''
#self.tn = None
def connect(self):
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 关键点禁止Nagle算法模仿网络调试助手强制使用纯raw socket连接但是导致TCP收不到数据 2026/6/2
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #停用
#self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) #telnet
self.socket.bind(("127.0.0.1", self.local_port))
self.socket.connect((self.remote_ip, self.remote_port))
self.socket.setblocking(False)
self.running = True
print(f"[TCP-UART] {self.dev_name} 连接成功: local={self.local_port} remote={self.remote_port}")
#telnet
#self.tn = telnetlib.Telnet("127.0.0.1",{self.remote_port},timeout=5)
return True
except Exception as e:
print(f"[TCP-UART] {self.dev_name} 连接失败: {e}")
return False
'''
# 去除转义FF的函数 2026/6/2,可能有安全问题
def unescape_ff(raw:bytes)->bytes:
buf = bytearray()
i = 0
n = len(raw)
while i<n:
buf.append(raw[i])
#当前字节FF下一个字节FF跳过下一个字节只保留一个
if raw[i] == 0xFF and i+1<n and raw[i+1]==0xFF:
i +=1
i +=1
return bytes(buf)
'''
# 去除转义FF的函数 2026/6/2
def unescape_ff(self, raw:bytes)->bytes:
out = bytearray()
idx=0
length = len(raw)
while idx<length:
cur = raw[idx]
out.append(cur)
if cur == 0xff and (idx + 1 < length) and raw[idx+1]==0xff:
idx+=1
idx+=1
return bytes(out)
def receive_loop(self):
from midware.core.bus_udp_decoupler import bus_recv_queues
from midware.config.base_config import DEVICE_CONFIG_DICT
while self.running:
try:
#data = self.tn.read_some()
data = self.socket.recv(8192)#4096修改为16384 2026/5/12 tmy 65536->8192 2026/5/15
if not data:
time.sleep(0.001)
continue
self.tcp_buf += data #新数据追加到缓存尾部2026/6/2
dev_name = None
protocol = None
for name, cfg in DEVICE_CONFIG_DICT.items():
if cfg.get("tcp_bus_port") == self.remote_port:
dev_name = name
protocol = cfg.get("protocol")
break
if not dev_name or not protocol:
continue
#循环拆包大多数情况是只跑0~1次循环 2026/6/2
while True:
if len(self.tcp_buf)>self.MAX_LEN * 2:
self.tcp_buf = self.tcp_buf[-1000:]
h_pos = self.tcp_buf.find(self.HEAD)
if h_pos ==-1:
break
#截断帧头前面垃圾
if h_pos>0:
self.tcp_buf = self.tcp_buf[h_pos:]
#缓存不足最小帧,直接退出
if(len(self.tcp_buf)<self.MIN_LEN):
break
#从帧头向后查找结束符号
t_pos = self.tcp_buf.find(self.TAIL,2)
if t_pos == -1:
break
frame = self.tcp_buf[:t_pos+2]
#logger.info(f'去除转义FF帧前的有问题帧内容{frame.hex()}')
if(self.MIN_LEN<= len(frame) <= self.MAX_LEN):
#去除转义FF
#logger.info(f'去除转义FF帧内容')
real_data = self.unescape_ff(frame)
#logger.info(f'去除转义FF帧内容{real_data.hex()}')
#入队
try:
q = bus_recv_queues.get(protocol)
if q:
try:
q.put_nowait({
"dev_name": dev_name,
"raw_data": real_data,
"protocol": protocol,
})
logger.debug(f'接受TCP重定向数据存入bus_recv_queues内容{real_data.hex()}')
except Full as e:
print("队列已满",e)
except Exception as e:
print("其他异常",repr(e))
except:
pass
#切掉已经解析的帧
self.tcp_buf = self.tcp_buf[(t_pos+2) :]
'''
# 匹配设备名
dev_name = None
protocol = None
for name, cfg in DEVICE_CONFIG_DICT.items():
if cfg.get("tcp_bus_port") == self.remote_port:
dev_name = name
protocol = cfg.get("protocol")
break
if not dev_name or not protocol:
continue
logger.info(f'接受TCP重定向数据存入bus_recv_queues内容{data.hex()}')
# 推入现有接收队列(完全兼容你原有架构)
q = bus_recv_queues.get(protocol)
if q:
try:
q.put_nowait({
"dev_name": dev_name,
"raw_data": data,
"protocol": protocol,
})
except:
pass
'''
except BlockingIOError:
time.sleep(0.001)
except Exception as e:
print(f"[TCP-UART] {self.dev_name} 接收异常: {e}")
break
self.running = False
def start(self):
if self.connect():
self.thread = threading.Thread(target=self.receive_loop, daemon=True)
self.thread.start()
tcp_clients[self.dev_name] = self
def stop(self):
self.running = False
if self.socket:
self.socket.close()
# ======================
# 批量启动所有UART TCP客户端
# ======================
def start_all_tcp_uart_clients():
from midware.config.base_config import DEVICE_CONFIG_DICT
for dev_name, cfg in DEVICE_CONFIG_DICT.items():
tcp_local = cfg.get("tcp_local_port", 1)
tcp_bus = cfg.get("tcp_bus_port", 1)
# 只启动端口 !=1 的UART设备
if tcp_local != 1 and tcp_bus != 1:
client = TCPUARTClient(
dev_name=dev_name,
local_port=tcp_local,
remote_port=tcp_bus
)
client.start()
time.sleep(0.1)