first commit

This commit is contained in:
2026-06-16 15:40:19 +08:00
commit 8ba6324244
97 changed files with 6216 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
# midware/drivers/__init__.py
"""
总线驱动模块:统一管理所有总线驱动的导入和初始化
"""
from .base_driver import BaseBusDriver
from .bus_1553b import Bus1553BDriver
from .bus_can import BusCANDriver
from .bus_uart import BusUARTDriver
from .bus_ad import BusADDriver
from .bus_oc import BusOCDriver
from .bus_network import BusNetworkDriver
from loguru import logger
# 驱动映射表:协议名称 → 驱动类与bus_udp_decoupler.py中的协议名对齐
DRIVER_MAPPING = {
"1553B": Bus1553BDriver,
"CAN": BusCANDriver,
"UART": BusUARTDriver,
"AD": BusADDriver,
"OC": BusOCDriver,
"网络": BusNetworkDriver
}
def create_bus_driver(protocol: str, config: dict = None) -> BaseBusDriver:
"""
工厂函数:根据协议名称创建驱动实例
:param protocol: 协议名称(如"1553B"
:param config: 驱动配置
:return: 驱动实例
"""
if protocol not in DRIVER_MAPPING:
raise ValueError(f"未知协议 {protocol},无对应驱动")
driver_class = DRIVER_MAPPING[protocol]
driver = driver_class(config=config)
logger.info(f"创建 {protocol} 驱动实例成功:{driver.__class__.__name__}")
return driver

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,78 @@
# midware/drivers/base_driver.py
"""
Tan mingyan
2026/3/9
驱动基类base_driver.py—— 定义标准接口(核心)
所有总线驱动继承该基类,保证接口统一,解耦 bus_udp_decoupler.py 与具体驱动实现:
"""
from abc import ABC, abstractmethod
from typing import Optional, Dict, Any
from loguru import logger
class BaseBusDriver(ABC):
"""
总线驱动基类:定义所有总线必须实现的标准接口
遵循「开闭原则」:新增总线只需继承该类实现接口,无需修改现有代码
"""
def __init__(self, config: Optional[Dict[str, Any]] = None):
"""
初始化驱动
:param config: 驱动配置如波特率、端口、IP等
"""
self.config = config or {}
self.is_connected = False # 驱动连接状态
self._init_config() # 初始化配置
def _init_config(self):
"""初始化默认配置(子类可重写)"""
logger.info(f"初始化 {self.__class__.__name__} 默认配置")
@abstractmethod
def connect(self) -> bool:
"""
连接总线如打开串口、建立CAN通道、连接1553B板卡
:return: 连接成功返回True失败返回False
"""
pass
@abstractmethod
def disconnect(self) -> bool:
"""
断开总线连接
:return: 断开成功返回True失败返回False
"""
pass
@abstractmethod
def send(self, data: bytes, **kwargs) -> bool:
"""
发送数据到总线
:param data: 待发送的原始字节数据
:param kwargs: 扩展参数如优先级、地址、帧ID等
:return: 发送成功返回True失败返回False
"""
pass
@abstractmethod
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
"""
从总线接收数据
:param timeout: 接收超时时间(秒)
:param kwargs: 扩展参数(如过滤条件、地址等)
:return: 接收到的字节数据,超时/失败返回None
"""
pass
def check_status(self) -> bool:
"""
检查驱动状态(默认实现,子类可重写)
:return: 驱动正常返回True异常返回False
"""
return self.is_connected
def __del__(self):
"""析构函数:自动断开连接"""
if self.is_connected:
self.disconnect()
logger.info(f"{self.__class__.__name__} 自动断开连接")

View File

@@ -0,0 +1,100 @@
"""
Tan mingyan
2026/3/9
2. 具体驱动实现(以 1553B 为例bus_1553b.py
其他总线CAN/UART/AD 等)按相同逻辑实现,仅需重写基类接口:
"""
# midware/drivers/bus_1553b.py
from .base_driver import BaseBusDriver
from typing import Optional, Dict, Any
from loguru import logger
import time
class Bus1553BDriver(BaseBusDriver):
"""1553B总线驱动实现适配实际1553B板卡/仿真器)"""
def _init_config(self):
"""初始化1553B默认配置"""
# 默认配置可从yaml文件加载
self.config.setdefault("board_id", 0) # 板卡ID
self.config.setdefault("bc_address", 0x01) # BC地址
self.config.setdefault("rt_address", 0x02) # RT地址
self.config.setdefault("baudrate", 1000000) # 波特率1Mbps
logger.info(f"1553B驱动配置初始化完成{self.config}")
def connect(self) -> bool:
"""连接1553B板卡/仿真器"""
try:
# 实际逻辑调用1553B板卡SDK的连接接口
# 示例self.board = SDK_1553B.connect(board_id=self.config["board_id"])
time.sleep(0.1) # 模拟连接耗时
self.is_connected = True
logger.info("1553B总线连接成功")
return True
except Exception as e:
logger.error(f"1553B总线连接失败{str(e)}")
self.is_connected = False
return False
def disconnect(self) -> bool:
"""断开1553B连接"""
try:
# 实际逻辑调用SDK的断开接口
# self.board.disconnect()
self.is_connected = False
logger.info("1553B总线断开成功")
return True
except Exception as e:
logger.error(f"1553B总线断开失败{str(e)}")
return False
def send(self, data: bytes, **kwargs) -> bool:
"""发送1553B数据帧"""
if not self.is_connected:
logger.error("1553B驱动未连接发送失败")
return False
try:
# 扩展参数:优先级、子地址等
priority = kwargs.get("priority", 1) # 1553B最高优先级
sub_address = kwargs.get("sub_address", 0x00)
# 实际逻辑调用SDK发送1553B帧
logger.info(
f"1553B发送数据优先级{priority}|子地址:{sub_address}"
f"数据:{data.hex()}|长度:{len(data)}字节"
)
# self.board.send_frame(
# bc_addr=self.config["bc_address"],
# rt_addr=self.config["rt_address"],
# sub_addr=sub_address,
# data=data,
# priority=priority
# )
return True
except Exception as e:
logger.error(f"1553B数据发送失败{str(e)}")
return False
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
"""接收1553B数据帧"""
if not self.is_connected:
logger.error("1553B驱动未连接接收失败")
return None
try:
# 实际逻辑调用SDK接收1553B帧
# frame = self.board.recv_frame(timeout=timeout)
# if frame:
# return frame.data
# return None
# 模拟接收(替换为真实逻辑)
time.sleep(0.001) # 模拟接收耗时
# mock_data = b"\x01\x02\x03\x04\x05" # 模拟1553B数据
# logger.debug(f"1553B接收数据{mock_data.hex()}")
#return mock_data
return None
except Exception as e:
logger.error(f"1553B数据接收失败{str(e)}")
return None

190
midware/drivers/bus_ad.py Normal file
View File

@@ -0,0 +1,190 @@
"""
Tan mingyan
2026/3/9
AD 驱动示例bus_ad.py—— 补充参考
"""
# midware/drivers/bus_can.py
from .base_driver import BaseBusDriver
from typing import Optional, Dict, Any
from loguru import logger
import time
# 导入全局配置字典需确保main.py已提前加载CSV配置
from midware.config.base_config import DEVICE_CONFIG_DICT, get_device_config
import midware.config.base_config as base_config
from .base_driver import BaseBusDriver
from .renode_agent import renode
from midware.config.base_config import DEVICE_CONFIG_DICT
class BusADDriver(BaseBusDriver):
"""uart总线驱动实现适配"""
def _init_config(self):
self.config.setdefault("channel", 0) # CAN通道
self.config.setdefault("baudrate", 500000) # 500kbps
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
logger.info(f"UART驱动配置初始化完成{self.config}")
def connect(self) -> bool:
try:
# 实际逻辑打开CAN通道
time.sleep(0.05)
self.is_connected = True
logger.info("CAN总线连接成功")
return True
except Exception as e:
logger.error(f"CAN总线连接失败{str(e)}")
return False
def disconnect(self) -> bool:
try:
self.is_connected = False
logger.info("CAN总线断开成功")
return True
except Exception as e:
logger.error(f"CAN总线断开失败{str(e)}")
return False
def send(self, data: bytes, **kwargs) -> bool:
if not self.is_connected:
logger.error("CAN驱动未连接发送失败")
return False
'''
try:
frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
is_extended = kwargs.get("is_extended", False) # 是否扩展帧
logger.info(
f"CAN发送数据帧ID0x{frame_id:X}|扩展帧:{is_extended}"
f"数据:{data.hex()}|长度:{len(data)}字节"
)
# 实际逻辑调用AD驱动发送接口
# 获取优先级,设备参数,地址等参数
return True
except Exception as e:
logger.error(f"CAN数据发送失败{str(e)}")
return False
'''
# ========== 核心步骤1从kwargs提取设备名称 ==========
#dev_name = kwargs.get("dev_name")[0]
dev_name = kwargs.get("dev_name")#2026/4/10
if not dev_name:
logger.error("AD发送失败kwargs中未传入dev_name设备名称")
return False
# ========== 核心步骤2从全局配置字典查找设备参数 ==========
# 检查全局配置是否加载
if not base_config.DEVICE_CONFIG_DICT:
logger.error("AD发送失败全局配置DEVICE_CONFIG_DICT未加载")
return False
# 查找当前设备的配置
#device_config = base_config.DEVICE_CONFIG_DICT.get(dev_name)
device_config = get_device_config().get(dev_name)
if not device_config:
logger.error(f"AD发送失败设备 {dev_name} 未在DEVICE_CONFIG_DICT中配置")
return False
# 提取关键配置项(兼容配置项缺失的情况)
try:
# 内存基地址转为16进制整数如"0x80000000" → 0x80000000
# mem_base_addr = int(str(device_config.get("base_addr", "0x0")), 16)#mem_base_addr = int(device_config.get("base_addr", "0x0"), 16)#内存基地址
mem_base_addr = int(device_config.get("base_addr", 0))
# 内存偏移地址
#mem_offset = int(str(device_config.get("offset_addr", "0x0")), 16)#内存偏移地址
mem_offset = int(device_config.get("offset_addr", 0))
# 发送缓存区大小(字节)
send_buffer_size = int(str(device_config.get("send_cache", 1024)))#发送缓存区大小
# 接收缓存区大小(字节)
recv_buffer_size = int(device_config.get("recv_cache", 1024))#接收缓存区大小
except (ValueError, TypeError) as e:
logger.error(f"AD发送失败{dev_name} 配置参数解析错误 → {str(e)}")
return False
# ========== 核心步骤3使用配置项处理发送逻辑 ==========
# 1. 校验数据长度不超过发送缓存区大小
if len(data) > send_buffer_size:
logger.error(
f"AD发送失败{dev_name} 数据长度 {len(data)} 超过发送缓存区大小 {send_buffer_size}"
)
return False
# 2. 模拟AD指令发送结合内存地址
logger.info(
f"AD发送指令设备{dev_name}"
f"内存基地址0x{mem_base_addr:X}偏移地址0x{mem_offset:X}"
f"发送缓存区:{send_buffer_size}B接收缓存区{recv_buffer_size}B"
f"数据:{data.hex()}|长度:{len(data)}字节"
)
# 实际业务逻辑:
# - 将数据写入指定内存地址mem_base_addr + mem_offset
# - 配置接收缓存区大小
# - 调用AD采集卡SDK发送指令
# ad_card.write_mem(mem_base_addr + mem_offset, data, send_buffer_size)
#AD0-AD6的寄存器写入策略
if(dev_name == "正电压采集热敏量采集"):
for i in range(4):
logger.info(f"AD发送指令{i}")
for j in range(14):
logger.info(f"AD发送指令{j}")
target_addr = mem_base_addr + mem_offset + i * 32 + j*8
canshu = data[2*(i*14+j)]*256 + data[2*(i*14+j)+1]# 2字节
sysbus_cmd = f"sysbus WriteDoubleWord {target_addr:#010x} {canshu:#006x}"
print(sysbus_cmd)
pass
## 外设名称+函数名+字符串参数 adc1 LoadRegistersFromHexString "55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA55AA"'
ad_cmd = f'{dev_name} LoadRegistersFromHexString "{data.hex()}"'
print(ad_cmd)
logger.info(f"AD发送指令{ad_cmd}")
response = renode.send_sync(ad_cmd,10)#2026/4/10
ad_cmd2 = f'{dev_name} ReadAllChannelsToString02'
logger.info(f"AD发送指令{ad_cmd2}")
response = renode.send_sync(ad_cmd2,10)#2026/4/10
return True
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
if not self.is_connected:
logger.error("ADC驱动未连接接收失败")
return None
try:
# 模拟接收
#遍历所有AD外设循环发送读取指令例如 adc1 ReadAllChannelsToString02
#接收renode回复
time.sleep(0.51)
'''
mock_data = b"\x11\x22\x33\x44"
logger.debug(f"AD接收数据{mock_data.hex()}")
#2026/4/10
dev_name = kwargs.get("dev_name")
if not dev_name:
logger.error("AD接收失败kwargs中未传入dev_name设备名称")
return None
cfg = DEVICE_CONFIG_DICT[dev_name]
base = int(cfg["内存基地址"], 16)
offset = int(cfg["内存偏移地址"], 16)
addr = base + offset
# ======================
# 调用 Renode 读取数据
# ======================
cmd = f"read 0x{addr:X}"
hex_str = renode.send_sync(cmd)
#return bytes.fromhex(hex_str.strip())
logger.info(f"AD接收数据{mock_data.hex()}")
return mock_data
'''
return None
except Exception as e:
logger.error(f"AD数据接收失败{str(e)}")
return None

133
midware/drivers/bus_can.py Normal file
View File

@@ -0,0 +1,133 @@
"""
Tan mingyan
2026/3/9
CAN 驱动示例bus_can.py—— 补充参考
"""
# midware/drivers/bus_can.py
from .base_driver import BaseBusDriver
from typing import Optional, Dict, Any
from loguru import logger
import time
from midware.config.base_config import get_device_config
from .base_driver import BaseBusDriver
from .renode_agent import renode
from midware.config.base_config import DEVICE_CONFIG_DICT
class BusCANDriver(BaseBusDriver):
"""CAN总线驱动实现适配CANoe/CANalyzer/USB-CAN"""
def _init_config(self):
self.config.setdefault("channel", 0) # CAN通道
self.config.setdefault("baudrate", 500000) # 500kbps
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
logger.info(f"CAN驱动配置初始化完成{self.config}")
def connect(self) -> bool:
try:
# 实际逻辑打开CAN通道
time.sleep(0.05)
self.is_connected = True
logger.info("CAN总线连接成功")
return True
except Exception as e:
logger.error(f"CAN总线连接失败{str(e)}")
return False
def disconnect(self) -> bool:
try:
self.is_connected = False
logger.info("CAN总线断开成功")
return True
except Exception as e:
logger.error(f"CAN总线断开失败{str(e)}")
return False
def send(self, data: bytes, **kwargs) -> bool:
if not self.is_connected:
logger.error("CAN驱动未连接发送失败")
return False
# ========== 核心步骤1从kwargs提取设备名称 ==========
dev_name = kwargs.get("dev_name")#2026/4/10
if not dev_name:
logger.error("CAN发送失败kwargs中未传入dev_name设备名称")
return False
device_config = get_device_config().get(dev_name)
if not device_config:
logger.error(f"CAN发送失败设备 {dev_name} 未在DEVICE_CONFIG_DICT中配置")
return False
try:
frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
is_extended = kwargs.get("is_extended", False) # 是否扩展帧
logger.info(
f"CAN发送数据帧ID0x{frame_id:X}|扩展帧:{is_extended}"
f"数据:{data.hex()}|长度:{len(data)}字节"
)
# logger.info(
# #f"UART发送数据帧ID0x{frame_id:X}|扩展帧:{is_extended}"
# f"CAN发送数据{data.hex()}|长度:{len(data)}字节"
# )
## 外设名称+函数名+字符串参数 uart24 WriteRXFIFODataString "0xEB9000C571000827C00000BC1003190000106C02AC020101FF5716"'
## 收到的CAN帧可能是多帧连在一起需要拆分单帧保证虚拟平台能读取。
frame_len = 11
frame_list = []
total =len(data)
#步长11字节截取完整帧,不满足的丢弃(尾帧长度可以不满11不丢弃)
end = 0
for start in range(0,total, frame_len):
'''
if end>total:
break
end = start + frame_len
frame_list.append(data[start:end])
sysbus_cmd = f'{dev_name} SendRxBufferDataString "0x{data[start:end].hex()}"'
#print(sysbus_cmd)
logger.info(f"CAN发送指令{sysbus_cmd}")
response = renode.send_sync(sysbus_cmd)#2026/4/10
time.sleep(0.0001)
'''
#计算当前帧结束的位置
end = min(start + frame_len, total)
frame = data[start:end]
#尾帧长度不足11时补0到11字节
if len(frame)<frame_len:
#补0
frame = frame.ljust(frame_len, b'\x00')
frame_list.append(frame)
sysbus_cmd = f'{dev_name} SendRxBufferDataString "0x{frame.hex()}"'
logger.info(f"CAN发送指令{sysbus_cmd}")
response = renode.send_sync(sysbus_cmd,timeout_ms=2)#2026/4/10
# response = renode.enqueue_cmd(sysbus_cmd)#2026/6/4
time.sleep(0.0001)
#sysbus_cmd = f'{dev_name} SendRxBufferDataString "0x{data.hex()}"'
#print(sysbus_cmd)
#logger.info(f"CAN发送指令{sysbus_cmd}")
#response = renode.send_sync(sysbus_cmd)#2026/4/10
# 实际逻辑调用CAN驱动发送接口
return True
except Exception as e:
logger.error(f"CAN数据发送失败{str(e)}")
return False
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
if not self.is_connected:
logger.error("CAN驱动未连接接收失败")
return None
try:
# 模拟接收
time.sleep(0.001)
# mock_data = b"\x11\x22\x33\x44"
# logger.debug(f"CAN接收数据{mock_data.hex()}")
# return mock_data
return None
except Exception as e:
logger.error(f"CAN数据接收失败{str(e)}")
return None

View File

@@ -0,0 +1,73 @@
"""
Tan mingyan
2026/3/9
network 驱动示例bus_network .py—— 补充参考
"""
# midware/drivers/bus_can.py
from .base_driver import BaseBusDriver
from typing import Optional, Dict, Any
from loguru import logger
import time
class BusNetworkDriver(BaseBusDriver):
"""network 总线驱动实现(适配)"""
def _init_config(self):
self.config.setdefault("channel", 0) # CAN通道
self.config.setdefault("baudrate", 500000) # 500kbps
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
logger.info(f"UART驱动配置初始化完成{self.config}")
def connect(self) -> bool:
try:
# 实际逻辑打开CAN通道
time.sleep(0.05)
self.is_connected = True
logger.info("CAN总线连接成功")
return True
except Exception as e:
logger.error(f"CAN总线连接失败{str(e)}")
return False
def disconnect(self) -> bool:
try:
self.is_connected = False
logger.info("CAN总线断开成功")
return True
except Exception as e:
logger.error(f"CAN总线断开失败{str(e)}")
return False
def send(self, data: bytes, **kwargs) -> bool:
if not self.is_connected:
logger.error("CAN驱动未连接发送失败")
return False
try:
frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
is_extended = kwargs.get("is_extended", False) # 是否扩展帧
logger.info(
f"CAN发送数据帧ID0x{frame_id:X}|扩展帧:{is_extended}"
f"数据:{data.hex()}|长度:{len(data)}字节"
)
# 实际逻辑调用CAN驱动发送接口
return True
except Exception as e:
logger.error(f"CAN数据发送失败{str(e)}")
return False
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
if not self.is_connected:
logger.error("CAN驱动未连接接收失败")
return None
try:
# 模拟接收
time.sleep(0.001)
# mock_data = b"\x11\x22\x33\x44"
# logger.debug(f"CAN接收数据{mock_data.hex()}")
# return mock_data
return None
except Exception as e:
logger.error(f"CAN数据接收失败{str(e)}")
return None

73
midware/drivers/bus_oc.py Normal file
View File

@@ -0,0 +1,73 @@
"""
Tan mingyan
2026/3/9
oc 驱动示例bus_oc.py—— 补充参考
"""
# midware/drivers/bus_can.py
from .base_driver import BaseBusDriver
from typing import Optional, Dict, Any
from loguru import logger
import time
class BusOCDriver(BaseBusDriver):
"""uart总线驱动实现适配"""
def _init_config(self):
self.config.setdefault("channel", 0) # CAN通道
self.config.setdefault("baudrate", 500000) # 500kbps
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
logger.info(f"UART驱动配置初始化完成{self.config}")
def connect(self) -> bool:
try:
# 实际逻辑打开CAN通道
time.sleep(0.05)
self.is_connected = True
logger.info("CAN总线连接成功")
return True
except Exception as e:
logger.error(f"CAN总线连接失败{str(e)}")
return False
def disconnect(self) -> bool:
try:
self.is_connected = False
logger.info("CAN总线断开成功")
return True
except Exception as e:
logger.error(f"CAN总线断开失败{str(e)}")
return False
def send(self, data: bytes, **kwargs) -> bool:
if not self.is_connected:
logger.error("CAN驱动未连接发送失败")
return False
try:
frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
is_extended = kwargs.get("is_extended", False) # 是否扩展帧
logger.info(
f"CAN发送数据帧ID0x{frame_id:X}|扩展帧:{is_extended}"
f"数据:{data.hex()}|长度:{len(data)}字节"
)
# 实际逻辑调用CAN驱动发送接口
return True
except Exception as e:
logger.error(f"CAN数据发送失败{str(e)}")
return False
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
if not self.is_connected:
logger.error("CAN驱动未连接接收失败")
return None
try:
# 模拟接收
time.sleep(0.001)
# mock_data = b"\x11\x22\x33\x44"
# logger.debug(f"CAN接收数据{mock_data.hex()}")
# return mock_data
return None
except Exception as e:
logger.error(f"CAN数据接收失败{str(e)}")
return None

131
midware/drivers/bus_uart.py Normal file
View File

@@ -0,0 +1,131 @@
"""
Tan mingyan
2026/3/9
uart 驱动示例bus_can.py—— 补充参考
"""
# midware/drivers/bus_can.py
from midware.config.base_config import get_device_config
from .base_driver import BaseBusDriver
from typing import Optional, Dict, Any
from loguru import logger
import time
from .base_driver import BaseBusDriver
from .renode_agent import renode
from midware.config.base_config import DEVICE_CONFIG_DICT
class BusUARTDriver(BaseBusDriver):
"""uart总线驱动实现适配"""
def _init_config(self):
self.config.setdefault("channel", 0) # CAN通道
self.config.setdefault("baudrate", 500000) # 500kbps
self.config.setdefault("frame_format", "standard") # 标准帧/扩展帧
logger.info(f"UART驱动配置初始化完成{self.config}")
def connect(self) -> bool:
try:
# 实际逻辑打开CAN通道
time.sleep(0.05)
self.is_connected = True
logger.info("CAN总线连接成功")
return True
except Exception as e:
logger.error(f"CAN总线连接失败{str(e)}")
return False
def disconnect(self) -> bool:
try:
self.is_connected = False
logger.info("CAN总线断开成功")
return True
except Exception as e:
logger.error(f"CAN总线断开失败{str(e)}")
return False
def send(self, data: bytes, **kwargs) -> bool:
if not self.is_connected:
logger.error("UART驱动未连接发送失败")
return False
# 查找当前设备的配置
# ========== 核心步骤1从kwargs提取设备名称 ==========
dev_name = kwargs.get("dev_name")#2026/4/10
if not dev_name:
logger.error("UART发送失败kwargs中未传入dev_name设备名称")
return False
device_config = get_device_config().get(dev_name)
if not device_config:
logger.error(f"UART发送失败设备 {dev_name} 未在DEVICE_CONFIG_DICT中配置")
return False
try:
#frame_id = kwargs.get("frame_id", 0x123) # CAN帧ID
#is_extended = kwargs.get("is_extended", False) # 是否扩展帧
logger.info(
#f"UART发送数据帧ID0x{frame_id:X}|扩展帧:{is_extended}"
f"UART发送数据{data.hex()}|长度:{len(data)}字节"
)
## 外设名称+函数名+字符串参数 uart24 WriteRXFIFODataString "0xEB9000C571000827C00000BC1003190000106C02AC020101FF5716"'
sysbus_cmd = f'{dev_name} WriteRXFIFODataString "0x{data.hex()}"'
#sysbus_cmd = f'can_a GetTxBufferDataString'
print(sysbus_cmd)
logger.info(f"UART发送指令{sysbus_cmd}")
response = renode.send_sync(sysbus_cmd,timeout_ms=20)#2026/4/10
#response = renode.enqueue_cmd(sysbus_cmd)#2026/6/4
# sysbus_cmd = f'uart24 GetTXFIFODataString'
# print(sysbus_cmd)
# response = renode.send_sync(sysbus_cmd)#2026/4/10
# 实际逻辑调用UART驱动发送接口
return True
except Exception as e:
logger.error(f"UART数据发送失败{str(e)}")
return False
def recv(self, timeout: float = 0.1, **kwargs) -> Optional[bytes]:
if not self.is_connected:
logger.error("UART驱动未连接接收失败")
return None
try:
#遍历所有UART外设循环发送读取指令例如 adc1 ReadAllChannelsToString02
# 模拟接收
time.sleep(0.91)
# mock_data = b"\x11\x22\x33\x44"
# logger.debug(f"CAN接收数据{mock_data.hex()}")
#logger.info(f"CAN接收数据")
# return mock_data
'''
mock_data = b"\x11\x22\x33\x44"
logger.debug(f"AD接收数据{mock_data.hex()}")
#2026/4/10
dev_name = kwargs.get("dev_name")
if not dev_name:
logger.error("AD接收失败kwargs中未传入dev_name设备名称")
return None
cfg = DEVICE_CONFIG_DICT[dev_name]
base = int(cfg["内存基地址"], 16)
offset = int(cfg["内存偏移地址"], 16)
addr = base + offset
# ======================
# 调用 Renode 读取数据
# ======================
cmd = f"read 0x{addr:X}"
hex_str = renode.send_sync(cmd)
#return bytes.fromhex(hex_str.strip())
logger.info(f"AD接收数据{mock_data.hex()}")
#return mock_data
'''
return None
except Exception as e:
logger.error(f"UART数据接收失败{str(e)}")
return None

View File

@@ -0,0 +1,528 @@
"""
2026/4/10
TMY
在connect_renode_19.py的基础上添加单例模式并添加总线类型映射
"""
# midware/drivers/renode_agent.py
import threading
import binascii
import subprocess
import time
import threading
import re
import statistics
from typing import Optional, Callable, List, Dict
from loguru import logger
# ======================
# 这里放你原来 connect_renode_19.py 的全部代码
# 只加 2 个东西:单例 + 总线类型映射
# ======================
# from . import DRIVER_MAPPING
# from midware.config.base_config import DEVICE_CONFIG_DICT
BUS_TYPE_MAP = {
0x11: "UART",
0x22: "CAN",
0x33: "1553B",
0x44: "NET",
0x55: "AD",
0x66: "OC",
}
# 合法的总线类型第一个字节(字符串形式,方便判断)
LEGAL_BUS_PREFIX = {"11", "22", "33", "44", "55", "66"}
class RenodeAgent:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self,
renode_path="renode",
script_path=None,
max_queue=50, # 指令队列最大长度(限流)
reconnect_retries=5, # 自动重连次数
socket_timeout=3):
if hasattr(self, "inited"):
return
self.inited = True
# ========= 你的原有代码 =========
# self.process = ...
# self.async_queue = ...
# self.start_renode()
# self.start_async_listener()
# 基础配置
self.renode_path = renode_path
self.script_path = script_path
self.process: Optional[subprocess.Popen] = None
self.lock = threading.Lock()
# 输出与异步
self.output_buffer: List[str] = []
self.async_running = False
self.async_callback: Optional[Callable[[str], None]] = None
# 限流队列
self.max_queue = max_queue
self.cmd_queue: List[str] = []
self.busy = False
# 自动重连
self.reconnect_retries = reconnect_retries
self.connected = False
# 时延统计
self.latency_stats: Dict[str, List[float]] = {
"send": [], "process": [], "total": []
}
# ====================== 超强过滤规则 ======================
self.filter_re = re.compile(
r"\(.*?\)" # 过滤 (machine) (TestPlatform) 等
r"|\d{2}:\d{2}:\d{2}\.\d+" # 过滤时间 20:15:26.1234
r"|renode>|\$|->" # 过滤提示符
r"|\x1b\[[0-9;]*m" # 过滤颜色码
r"|^\s*$" # 过滤空行
)
self.last_send_t =0.0
self.send_min_gap = 0.01 #控制下发间隔
'''
# 你原来的函数,完全不变
def send_sync(self, cmd: str, timeout=1.0):
# 你的原有逻辑
pass
def enqueue_cmd(self, cmd: str):
# 你的原有逻辑
pass
def start_async_listener(self, callback):
# 你的原有异步监听
pass
'''
def start(self) -> bool:
try:
args = [
self.renode_path,
"--console",
#"--disable-ansi-colors",
"-e", "set echo off; set line-editing off; set raw on"
]
if self.script_path:
args.extend(["-e", f"include @{self.script_path}"])
#args.extend(["-e", f"{self.script_path}"])
self.process = subprocess.Popen(
args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=0,
universal_newlines=True,
creationflags=subprocess.CREATE_NEW_CONSOLE #LWB
)
self.async_running = True
self.connected = True
threading.Thread(target=self._read_thread, daemon=True).start()
threading.Thread(target=self._queue_worker, daemon=True).start()
time.sleep(1)
self.clear_buffers()
print("✅ 仿真平台 启动成功")
sysbus_cmd = "logLevel 3" #3级-只显示error
response = renode.send_sync(sysbus_cmd)
return True
except Exception as e:
print(f"❌ 启动失败: {e}")
self.connected = False
return self._try_reconnect()
# -------------------------------------------------------------------------
# 后台读取线程(永远实时读取,永不堆积)
# -------------------------------------------------------------------------
def _read_thread(self):
while self.async_running and self.process.poll() is None:
try:
line = self.process.stdout.readline()
if not line:
time.sleep(0.001)
continue
cleaned = self.filter_line(line)
if not cleaned:
continue
with self.lock:
self.output_buffer.append(cleaned)
if self.async_callback:
self.async_callback(cleaned)
except Exception:
break
self.connected = False
print("⚠️ Renode 已断开")
self._try_reconnect()
# -------------------------------------------------------------------------
# 过滤垃圾输出
# -------------------------------------------------------------------------
def filter_line(self, line: str) -> Optional[str]:
res = self.filter_re.sub("", line).strip()
return res if res else None
# -------------------------------------------------------------------------
# 同步发送(带时延测量 + 限流),
# -------------------------------------------------------------------------
def send_sync(
self,
cmd: str,
timeout_ms: int = 20
) -> tuple[Optional[str], float, float, float]:
if not self.connected or not self.process:
return None, 0, 0, 0
# 限流:队列满则等待
while len(self.cmd_queue) >= self.max_queue:
time.sleep(0.001)
# self.clear_buffers() #同步发送不再清空全局输出缓冲区,缓冲区只有一部读取线程消费 2026/6/3
t0 = time.perf_counter()
# 发送
try:
self.process.stdin.write(f"{cmd}\n")
self.process.stdin.flush()
except:
return None, 0, 0, 0
t1 = time.perf_counter()
# 等待结果
timeout = time.time() + timeout_ms / 1000
while time.time() < timeout:
with self.lock:
if self.output_buffer:
res = "\n".join(self.output_buffer)
self.output_buffer.clear()
t2 = time.perf_counter()
send_lat = (t1 - t0) * 1000
proc_lat = (t2 - t1) * 1000
total_lat = (t2 - t0) * 1000
self.latency_stats["send"].append(send_lat)
self.latency_stats["process"].append(proc_lat)
self.latency_stats["total"].append(total_lat)
return res, send_lat, proc_lat, total_lat
time.sleep(0.001)
return None, 0, 0, 0
'''
# -------------------------------------------------------------------------
# 同步发送(有时延测量 + 限流,不等待回复),
# 2026/6/4
# -------------------------------------------------------------------------
def send_sync(
self,
cmd: str,
timeout_ms: int = 20
) -> tuple[Optional[str], float, float, float]:
if not self.connected or not self.process:
return None,0,0,0
#队列限流等待
while len(self.cmd_queue) >=self.max_queue:
time.sleep(0.001)
t0 = time.perf_counter()
try:
self.process.stdin.write(f"{cmd}\n")
self.process.stdin.flush()
except:
return None,0,0,0
t1 = time.perf_counter()
#【关键改动不在阻塞轮询收应答直接退出所有应答给dispacher消费】
send_lat = (t1 - t0)*1000
return None,send_lat, 0 , send_lat
'''
# -------------------------------------------------------------------------
# 异步监听
# -------------------------------------------------------------------------
def start_async_listener(self, callback: Callable[[str], None]):
self.async_callback = callback
def clear_buffers(self):
with self.lock:
self.output_buffer.clear()
# -------------------------------------------------------------------------
# 指令队列限流(后台异步发送)
# -------------------------------------------------------------------------
def enqueue_cmd(self, cmd: str):
if len(self.cmd_queue) < self.max_queue:
self.cmd_queue.append(cmd)
'''
def _queue_worker(self): #老版本,停用 2026、6、4
while self.async_running:
if not self.connected or self.busy or not self.cmd_queue:
time.sleep(0.001)
continue
self.busy = True
cmd = self.cmd_queue.pop(0)
self.send_sync(cmd, timeout_ms=1) #150->1
self.busy = False
'''
'''
def _queue_worker(self): #新版本2026、6、4
while self.async_running:
if not self.connected or not self.cmd_queue:
time.sleep(0.001)
continue
cmd = self.cmd_queue.pop(0)
self.send_sync(cmd) #150->1
logger.info(f" _queue_worker向管道发送数据{cmd}")
'''
def _queue_worker(self): #新版本2026、6、4
while self.async_running:
now = time.time()
#时间没有到让出CPU
if now - self.last_send_t < self.send_min_gap:
time.sleep(0.0005)
continue
if not self.connected or not self.cmd_queue:
time.sleep(0.001)
continue
cmd = self.cmd_queue.pop(0)
self.send_sync(cmd) #150->1
logger.info(f" _queue_worker向管道发送数据{cmd}")
# -------------------------------------------------------------------------
# 自动重连
# -------------------------------------------------------------------------
def _try_reconnect(self) -> bool:
print(f"🔌 尝试自动重连 ({self.reconnect_retries} 次)")
for i in range(self.reconnect_retries):
self.stop()
time.sleep(1)
if self.start():
print(f"✅ 第 {i+1} 次重连成功")
return True
time.sleep(1)
print("❌ 重连全部失败")
return False
# -------------------------------------------------------------------------
# 时延统计
# -------------------------------------------------------------------------
def get_latency_report(self) -> Dict[str, float]:
def stats(arr):
if not arr: return 0,0,0
return round(statistics.mean(arr),2), round(max(arr),2), round(min(arr),2)
return {
"send_avg,max,min": stats(self.latency_stats["send"]),
"process_avg,max,min": stats(self.latency_stats["process"]),
"total_avg,max,min": stats(self.latency_stats["total"]),
}
def clear_latency(self):
for k in self.latency_stats:
self.latency_stats[k].clear()
# -------------------------------------------------------------------------
# 停止
# -------------------------------------------------------------------------
def stop(self):
self.async_running = False
self.connected = False
try:
if self.process:
self.process.stdin.write("quit\n")
self.process.stdin.flush()
time.sleep(0.2)
self.process.terminate()
except:
pass
self.process = None
print("🔌 Renode 已停止")
# midware/drivers/renode_agent.py
# 第三步:异步数据分发(完全不影响你现有架构)
#在 renode_agent.py 里加全局分发回调,直接把数据投递到你现有的驱动 recv
# def renode_async_data_dispatcher(line: str):
# try:
# # 1. 转成字节
# byte_data = binascii.unhexlify(line.strip())
# print(byte_data)
# # 2. 解析头部 3 字节
# if len(byte_data) < 3:
# return
# bus_type_id = byte_data[0]
# machine_type = byte_data[1]
# machine_id = byte_data[2]
# payload = byte_data[3:]
# # 3. 映射协议类型
# protocol = BUS_TYPE_MAP.get(bus_type_id)
# if not protocol:
# return
# # 4. 找到对应设备(根据单机编号)
# dev_name = None
# for name, cfg in DEVICE_CONFIG_DICT.items():
# if cfg.get("协议类型") == protocol and cfg.get("单机编号") == machine_id:
# dev_name = name
# break
# if not dev_name:
# return
# # 5. 把数据 **直接放入你现有的总线接收队列**
# from midware.core.bus_udp_decoupler import bus_recv_queues
# q = bus_recv_queues[protocol]
# q.put({
# "dev_name": dev_name,
# "raw_data": payload,
# "protocol": protocol,
# })
# except Exception:
# pass
def renode_async_data_dispatcher(line: str):
try:
# -----------------------
# 🔥 延迟导入(放在函数内部,不会循环引用!)
# -----------------------
from midware.core.bus_udp_decoupler import bus_recv_queues
from midware.config.base_config import DEVICE_CONFIG_DICT
# print(f"异步监听到的数据{line}")
logger.info(
f"异步监听到的数据{line}"
)
# -----------------------
# 0.1. 基础过滤
# -----------------------
line = line.strip()
if len(line) < 6: # 至少3字节 = 6个十六进制字符
return
# -----------------------
# 0.2. 【关键】判断前2个字符是否是合法总线类型
# -----------------------
first_byte_str = line[:2]
if first_byte_str not in LEGAL_BUS_PREFIX:
# 不是我们要的总线数据 → 直接丢弃
return
# -----------------------
# 0.3. 判断整个字符串是否是合法十六进制(安全转换)
# -----------------------
def is_hex(s):
try:
int(s, 16)
return True
except:
return False
if not is_hex(line):
return
# 1. 把 Renode 返回的字符串转成字节,首先判断首字符是否为
byte_data = bytes.fromhex(line.strip())
if len(byte_data) < 3:
return
# 2. 解析 3 字节头部
bus_type_id = byte_data[0]
machine_type = byte_data[1]
machine_id = byte_data[2]
payload = byte_data[3:]
# 3. 映射协议名
protocol = BUS_TYPE_MAP.get(bus_type_id)
if not protocol:
return
# 4. 根据 协议类型 + 单机编号 → 找到设备名
dev_name = None
for name, cfg in DEVICE_CONFIG_DICT.items():
if (cfg.get("protocol") == protocol and
cfg.get("number") == machine_id):
dev_name = name
break
if not dev_name:
return
# 5. 放入你现有的接收队列(完全兼容你原来架构)
q = bus_recv_queues[protocol]
try:
q.put_nowait({
"dev_name": dev_name,
"raw_data": payload,
"protocol": protocol,
})
except:
pass
except Exception:
import traceback
traceback.print_exc()
# 在 renode_agent.py 末尾加这个函数
def start_renode_uart_redirect():
from midware.config.base_config import DEVICE_CONFIG_DICT
renode = RenodeAgent() # 单例
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)
if tcp_local != 1 and tcp_bus != 1:
term_name = f"term_{dev_name}"
cmd1 = f'emulation CreateServerSocketTerminal {tcp_bus} "{term_name}"'#临时注释2026/6/2
cmd2 = f'connector Connect sysbus.{dev_name} {term_name}'
print(f"[Renode] 配置UART重定向: {cmd1}")
renode.send_sync(cmd1, timeout_ms=2)
time.sleep(0.1)
print(f"[Renode] 连接外设: {cmd2}")
renode.send_sync(cmd2, timeout_ms=2)
time.sleep(0.2)
print("[Renode] UART重定向配置完成")
# 全局单例,全局唯一
renode = RenodeAgent(
renode_path = r"E:\simulation\1_simulation\卫星仿真平台_后端.exe", #r"E:\codes\1_simulation\卫星仿真平台_后端.exe", #r"C:\Users\PingCe\Desktop\1_simulation\卫星仿真平台_后端.exe", # Windows可写绝对路径如 C:/Renode/renode.exe
script_path = r'E:\simulation\1_simulation\generated.resc',#r'E:\codes\1_simulation\generated_771_ADC1.resc', #r'include @C:\Users\PingCe\Desktop\1_simulation\generated_771_ADC1.resc',
max_queue=25, # 最多同时排队5条指令防止压爆
reconnect_retries=5
)

View File

@@ -0,0 +1,226 @@
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)