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

0
midware/bus/__init__.py Normal file
View File

View File

@@ -0,0 +1,55 @@
'''
5. 缓存交互模块midware/bus/cache_handler.py
实现与 SJA1000/61580 共享缓存的交互提供WriteDoubleWord/ReadDoubleWord核心操作对接虚拟芯片
'''
from loguru import logger
class CacheHandler:
"""缓存交互层实现sysbus WriteDoubleWord/ReadDoubleWord操作"""
def __init__(self):
# 模拟共享缓存字典实现key=内存地址value=32位无符号整数
self.shared_cache = {}
logger.info("共享缓存初始化完成模拟SJA1000/61580")
def write_double_word(self, mem_addr, data):
"""
写入双字32位数据到共享缓存
:param mem_addr: 内存地址int
:param data: 16/32位数据int自动转为32位无符号整数
"""
if not isinstance(mem_addr, int) or not isinstance(data, int):
logger.error("写入缓存参数错误,地址/数据必须为整数")
return False
# 转为32位无符号整数
##给RENODE的写入的语句未定
self.shared_cache[mem_addr] = data & 0xFFFFFFFF
logger.debug(f"写入缓存:地址{hex(mem_addr)},数据{hex(self.shared_cache[mem_addr])}")
return True
def read_double_word(self, mem_addr):
"""
从共享缓存读取双字32位数据
:param mem_addr: 内存地址int
:return: 32位无符号整数地址不存在返回0
"""
if not isinstance(mem_addr, int):
logger.error("读取缓存参数错误,地址必须为整数")
return 0
data = self.shared_cache.get(mem_addr, 0)
logger.debug(f"读取缓存:地址{hex(mem_addr)},数据{hex(data)}")
return data
def clear_cache(self, mem_addr=None):
"""清空指定地址/全部缓存"""
if mem_addr:
if mem_addr in self.shared_cache:
del self.shared_cache[mem_addr]
logger.debug(f"清空缓存地址:{hex(mem_addr)}")
else:
self.shared_cache.clear()
logger.info("清空全部共享缓存")
return True
# 单例模式,全局唯一缓存实例
cache_handler = CacheHandler()

View File

View File

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,179 @@
import os
from loguru import logger
import threading
from midware.config.excel_config import load_csv_config
'''
3. 基础配置模块midware/config/base_config.py
定义全局配置常量初始化外设、UDP、总线基础参数预留配置动态加载接口
'''
# 基础配置 - UDP故障注入软件交互
UDP_CONFIG = {
"LOCAL_IP": "10.20.48.138", # 本地UDP地址用于回环测试,自测时候是"127.0.0.1",科学所是"192.168.0.150"","10.20.48.138"
"LOCAL_PORT": 8888, # 本地UDP端口
"FAULT_INJECT_IP": "10.20.48.129",# 故障注入软件IP用于回环测试,自测时候是"127.0.0.1",科学所是"192.168.0.130"","10.20.48.129"
"FAULT_INJECT_PORT": 18889, # 故障注入软件端口
"BUFFER_SIZE": 4096 # UDP缓冲区大小
}
# 基础配置 - 外设总线支持UART/CAN/1553B/AD/OC
BUS_CONFIG = {
"SUPPORT_BUS": ["UART", "CAN", "1553B", "AD", "OC"],
"MAX_DEVICE_NUM": 10, # 最大支持10个单机
"WRITE_BYTE_UART": 2, # UART每次写入2字节
"WRITE_BYTE_CAN": 2, # CAN每次写入2字节
"READ_BYTE_CAN": 1, # CAN每次读取1字节
"WRITE_BYTE_1553B": 2, # 1553B每次写入2字节
"WRITE_BYTE_AD": 2, # AD每次写入2字节
"READ_BYTE_OC": 1 # OC每次读取1字节
}
# 基础配置 - UART芯片寄存器初始写死后续由chip_config.ini加载
UART_CHIP_CONFIG = {
"TBR": 0x00, # 发送FIFO
"FIFO_STATUS": 0x04, # FIFO状态寄存器
"FRAME_COUNT": 0x08, # 帧计数
"FIFO_REMAIN": 0x0C, # FIFO剩余字节数
"CLOCK_CONFIG": 0x10, # 时钟配置
"SCRAMBLE": 0x14, # 加解扰使能
"RESET": 0x7C # 复位
}
# 全局外设配置字典,存储已启用的外设信息
# ==================== 全局配置(唯一数据源,仅在此文件定义)====================
# 初始化为空字典由init_base_config()初始化
DEVICE_CONFIG_DICT = {} #{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}, 9: {}, 10: {}}
# 配置读写锁(保证多线程安全,防止读写冲突)
CONFIG_LOCK = threading.RLock()
# ==================== 日志初始化(工程全局)====================
def init_logger():
os.makedirs("logs", exist_ok=True)
# 运行日志+错误日志分离
logger.add("logs/run_{time:YYYYMMDDHHmmss}.log", level="INFO",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}")
logger.add("logs/error_{time:YYYYMMDDHHmmss}.log", level="ERROR",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {file}:{line} | {message}")
# 工程启动时自动初始化日志
init_logger()
'''
# 加载xlsx文件
def init_base_config():
"""初始化基础配置,加载默认外设配置"""
try:
# 加载默认Excel配置后续实现
from midware.config.excel_config import load_excel_config
default_excel = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "config_files/device_config.xlsx")
if os.path.exists(default_excel):
DEVICE_CONFIG_DICT = load_excel_config(default_excel)
logger.info(f"加载默认Excel配置{len(DEVICE_CONFIG_DICT)}个外设")
else:
logger.warning(f"默认配置文件不存在:{default_excel},使用空配置")
logger.info("基础配置初始化完成")
except Exception as e:
logger.error(f"基础配置初始化失败:{str(e)}", exc_info=True)
raise e
'''
'''
# midware/config/base_config.py 中init_base_config函数
def init_base_config():
"""初始化基础配置,加载默认外设配置"""
global DEVICE_CONFIG_DICT
try:
# 替换为CSV加载方法
#from midware.config.excel_config import load_csv_config
default_csv = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "config_files/device_config.csv")
if os.path.exists(default_csv):
#global DEVICE_CONFIG_DICT
DEVICE_CONFIG_DICT = load_csv_config(default_csv)
logger.info(f"加载默认CSV配置{len(DEVICE_CONFIG_DICT)}个外设")
else:
logger.warning(f"默认配置文件不存在:{default_csv},使用空配置")
DEVICE_CONFIG_DICT = {}
logger.info("基础配置初始化完成")
except Exception as e:
logger.error(f"基础配置初始化失败:{str(e)}", exc_info=True)
DEVICE_CONFIG_DICT = {}
#raise e #注释掉这行,避免程序崩溃
def update_device_config(new_config: dict):
"""
对外提供配置更新方法如UI勾选外设、导入新配置时调用
:param new_config: 新的外设配置字典格式与DEVICE_CONFIG_DICT一致
"""
global DEVICE_CONFIG_DICT
if isinstance(new_config, dict):
DEVICE_CONFIG_DICT = new_config
logger.info(f"外设配置已更新,当前共{len(DEVICE_CONFIG_DICT)}个外设")
# 配置更新后自动触发UDP套接字重新加载关键保证UDP端口与配置同步
from midware.network.udp_handler import udp_server
udp_server.reload_sockets()
else:
logger.error("配置更新失败:新配置非字典类型")
# 工程启动时自动初始化日志
#init_logger()
'''
# ==================== 配置核心方法(对外暴露,唯一入口)====================
def init_base_config(csv_path: str = None):
"""
初始化外设配置:工程启动时**唯一调用**加载CSV配置到DEVICE_CONFIG_DICT
:param csv_path: 配置文件路径默认取工程根目录config_files/device_config.csv
"""
global DEVICE_CONFIG_DICT # 声明修改全局变量
if csv_path is None:
# 自动拼接工程根目录的配置文件路径
csv_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
"config_files/device_config.csv"
)
# 加锁保证初始化原子性,防止多线程同时修改
with CONFIG_LOCK:
try:
if os.path.exists(csv_path):
# 加载CSV配置并覆盖全局变量
DEVICE_CONFIG_DICT = load_csv_config(csv_path)
logger.info(f"配置初始化成功|加载{len(DEVICE_CONFIG_DICT)}个外设|配置文件:{csv_path}")
else:
DEVICE_CONFIG_DICT = {}
logger.error(f"配置初始化失败|配置文件不存在:{csv_path}")
except Exception as e:
DEVICE_CONFIG_DICT = {}
logger.error(f"配置初始化异常|{str(e)}", exc_info=True)
return DEVICE_CONFIG_DICT
def update_device_config(new_config: dict):
"""
唯一配置更新入口修改配置后自动同步UDP端口
【关键】延迟导入udp_server避免模块顶层循环导入
"""
global DEVICE_CONFIG_DICT
with CONFIG_LOCK:
if not isinstance(new_config, dict):
logger.error("配置更新失败|新配置非字典类型")
return False
# 覆盖全局配置
DEVICE_CONFIG_DICT = new_config.copy() # 深拷贝,防止外部修改源字典
logger.info(f"配置更新成功|当前外设数量:{len(DEVICE_CONFIG_DICT)}")
# 延迟导入:仅在函数内导入,避免模块加载时循环依赖
try:
from midware.network.udp_handler import udp_server
udp_server.reload_sockets(new_config) # 传参同步配置,不依赖全局引用
except ImportError as e:
logger.warning(f"UDP配置同步失败未找到UDP模块{str(e)}")
return True
def get_device_config():
"""
全局配置**只读接口**:所有其他模块必须通过此方法获取配置
返回副本,防止外部修改原全局变量导致数据混乱
"""
with CONFIG_LOCK:
return DEVICE_CONFIG_DICT.copy() # 返回深拷贝,彻底隔离原变量
def get_udp_config():
"""UDP基础配置只读接口"""
return UDP_CONFIG.copy()

View File

@@ -0,0 +1,11 @@
名称,协议类型,内存基地址,内存偏移地址,port,说明,发送缓存区大小,接收缓存区大小
光纤陀螺A,UART,0x20800000,0x0000,4000,Uart0,512,512
反作用轮A,CAN,0x20810000,0x0100,4001,CAN0,1024,1024
1553B0,1553B,0x20820000,0x0200,4002,测控模块,2048,2048
电压采集,AD,0x20830000,0x0300,4003,AD0,512,512
开关量输出,OC,0x20840000,0x0400,4004,OC0,512,512
光纤陀螺B,UART,0x20850000,0X0001,4005,UART1,512,512
星敏A,CAN,0x20860000,0x0101,4006,CAN1,512,512
数传A,1553B,0x20870000,0x0201,4007,1553B1,512,512
电流采集,AD,0x20880000,0x0301,4018,AD1,512,512
继电器输出,OC,0x20890000,0x0401,4009,OC1,512,512
1 名称 协议类型 内存基地址 内存偏移地址 port 说明 发送缓存区大小 接收缓存区大小
2 光纤陀螺A UART 0x20800000 0x0000 4000 Uart0 512 512
3 反作用轮A CAN 0x20810000 0x0100 4001 CAN0 1024 1024
4 1553B0 1553B 0x20820000 0x0200 4002 测控模块 2048 2048
5 电压采集 AD 0x20830000 0x0300 4003 AD0 512 512
6 开关量输出 OC 0x20840000 0x0400 4004 OC0 512 512
7 光纤陀螺B UART 0x20850000 0X0001 4005 UART1 512 512
8 星敏A CAN 0x20860000 0x0101 4006 CAN1 512 512
9 数传A 1553B 0x20870000 0x0201 4007 1553B1 512 512
10 电流采集 AD 0x20880000 0x0301 4018 AD1 512 512
11 继电器输出 OC 0x20890000 0x0401 4009 OC1 512 512

View File

@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
'''
Excel 配置解析excel_config.py基于openpyxl实现 Excel 文件解析提取外设名称、UDP 端口、协议类型、基地址等信息存入DEVICE_CONFIG_DICT。
'''
import openpyxl
from loguru import logger
import re
import pandas as pd
import os # 新增:用于删除临时文件
import sys
import io
# 设置标准输出和标准错误的编码为UTF-8
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
# 添加工程根目录到环境变量
# sys.path.append(os.path.dirname(os.path.abspath(__file__)))
def hex_to_int (hex_str):
"""处理十六进制字符串转整数(兼容带逗号 / 空格的格式,如 0x20800000、0x0000 1234
:param hex_str: 十六进制字符串
:return: 转换后的整数,失败返回 0
"""
if not isinstance (hex_str, str):
return 0 # 非字符串直接返回0
# 移除逗号、空格等非十六进制字符
clean_hex = re.sub (r'[,\s]', '', hex_str.strip ())
try:
return int (clean_hex, 16) if clean_hex else 0
except (ValueError, TypeError):
logger.warning (f"十六进制转换失败:{hex_str},默认返回 0")
return 0
def load_csv_config (file_path):
"""加载 Excel/CSV 外设配置文件,解析核心配置项
:param file_path: Excel/CSV文件路径xlsx/xls/csv
:return: 外设配置字典key = 外设名称value = 配置项字典
"""
device_config = {}
wb = None # 初始化工作簿对象
temp_excel = "file.xlsx" # CSV转换的临时Excel文件
try:
# 处理CSV文件转换为Excel后解析
if file_path.endswith('.csv'):
df = pd.read_csv(file_path)
df.to_excel(temp_excel, index=False)
file_path = temp_excel # 切换为临时Excel文件路径
# 打开工作簿兼容直接传入Excel文件的情况
wb = openpyxl.load_workbook(file_path, data_only=True)
ws = wb.active
logger.info(f"成功打开配置文件:{file_path},工作表:{ws.title}")
# 获取表头行,匹配核心配置列(兼容列名大小写 / 空格)
header = [cell.value.strip() if cell.value else "" for cell in ws[1]]
col_mapping = {
"名称": header.index([h for h in header if h.lower() == "名称"][0]) if [h for h in header if h.lower() == "名称"] else -1,
"协议类型": header.index([h for h in header if h.lower() in ["协议", "协议类型"]][0]) if [h for h in header if h.lower() in ["协议", "协议类型"]] else -1,
"内存基地址": header.index([h for h in header if h.lower() in ["内存基地址", "基地址"]][0]) if [h for h in header if h.lower() in ["内存基地址", "基地址"]] else -1,
"内存偏移地址": header.index([h for h in header if h.lower() in ["内存偏移地址", "偏移地址"]][0]) if [h for h in header if h.lower() in ["内存偏移地址", "偏移地址"]] else -1,
"UDP端口": header.index([h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]][0]) if [h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]] else -1,
"说明": header.index([h for h in header if h.lower() == "说明"][0]) if [h for h in header if h.lower() == "说明"] else -1,
"发送缓存区大小": header.index([h for h in header if h.lower() in ["发送缓存区大小", "发送缓存"]][0]) if [h for h in header if h.lower() in ["发送缓存区大小", "发送缓存"]] else -1,
"接收缓存区大小": header.index([h for h in header if h.lower() in ["接收缓存区大小", "接收缓存"]][0]) if [h for h in header if h.lower() in ["接收缓存区大小", "接收缓存"]] else -1,
}
# 校验核心列(名称、协议、基地址必须存在)
required_cols = ["名称", "协议类型", "内存基地址"]
for col in required_cols:
if col_mapping[col] == -1:
logger.error(f"配置文件缺少核心列:{col},请检查表头")
return device_config
# 遍历数据行(从第 2 行开始)
for row_num, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
if not row[col_mapping["名称"]]: # 外设名称为空则跳过
logger.warning(f"{row_num} 行:外设名称为空,跳过该行")
continue
dev_name = row[col_mapping["名称"]].strip()
# 解析单行配置,缺省项赋默认值
dev_info = {
"protocol": row[col_mapping["协议类型"]].strip().upper() if row[col_mapping["协议类型"]] else "",
"base_addr": hex_to_int(row[col_mapping["内存基地址"]]),
"offset_addr": hex_to_int(row[col_mapping["内存偏移地址"]]),
"udp_port": int(row[col_mapping["UDP端口"]]) if (row[col_mapping["UDP端口"]] and str(row[col_mapping["UDP端口"]]).isdigit()) else 8888,
"desc": row[col_mapping["说明"]].strip() if row[col_mapping["说明"]] else "",
"send_cache": int(row[col_mapping["发送缓存区大小"]]) if (row[col_mapping["发送缓存区大小"]] and str(row[col_mapping["发送缓存区大小"]]).isdigit()) else 512,
"recv_cache": int(row[col_mapping["接收缓存区大小"]]) if (row[col_mapping["接收缓存区大小"]] and str(row[col_mapping["接收缓存区大小"]]).isdigit()) else 512,
"enable": True # 默认启用该外设
}
# 校验协议类型合法性
if dev_info["protocol"] not in ["UART", "CAN", "1553B", "AD", "OC"]:
logger.warning(f"{row_num} 行 [{dev_name}]:协议类型 {dev_info['protocol']} 不合法,仅支持 UART/CAN/1553B/AD/OC跳过该行")
continue
device_config[dev_name] = dev_info
logger.debug(
f"解析外设配置:{dev_name} | {dev_info['protocol']} | 基地址 {hex(dev_info['base_addr'])} | "
f"端口号 {dev_info['udp_port']} | 发送缓存 {dev_info['send_cache']} | 接收缓存 {dev_info['recv_cache']} | 说明 {dev_info['desc']}"
)
logger.info(f"配置文件解析完成,共加载 {len(device_config)} 个有效外设配置")
print(f"配置文件解析完成,共加载 {len(device_config)} 个有效外设配置")
except FileNotFoundError:
logger.error(f"配置文件不存在:{file_path}")
print(f"配置文件不存在:{file_path}")
except Exception as e:
logger.error(f"配置文件解析失败:{str(e)}", exc_info=True)
print(f"配置文件解析失败:{str(e)}")
finally:
# 确保工作簿最终关闭(无论是否异常)
if wb:
try:
wb.close()
logger.debug("Excel工作簿已正常关闭")
except Exception as e:
logger.warning(f"关闭Excel工作簿失败{str(e)}")
# 删除CSV转换的临时Excel文件
if os.path.exists(temp_excel):
try:
os.remove(temp_excel)
logger.debug("临时Excel文件已删除")
except Exception as e:
logger.warning(f"删除临时Excel文件失败{str(e)}")
return device_config
# 测试代码(单独运行该文件时执行)
if __name__ == "__main__":
# test_config = load_csv_config("../../config_files/device_config.csv") # 替换为实际文件路径
test_config = load_csv_config("config_files/device_config.csv") # 替换为实际文件路径,难道这的根目录
print(test_config)

View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
'''
Excel 配置解析excel_config.py基于openpyxl实现 Excel 文件解析提取外设名称、UDP 端口、协议类型、基地址等信息存入DEVICE_CONFIG_DICT。
'''
'''
### 核心功能说明
1. **格式兼容**支持解析Excel中**带逗号/空格的十六进制地址**(如`0x20800000`→`0x20800000`),自动清洗非十六进制字符。
2. **列名容错**:兼容表头列名的**大小写、别名**(如“基地址”/“内存基地址”、“发送缓存”/“发送缓存区大小”)。
3. **缺省值赋值**未配置的项赋予合理默认值如UDP端口默认8888、缓存大小默认512
4. **合法性校验**
- 必须包含**名称、协议类型、内存基地址**核心列,否则解析失败;
- 协议类型仅支持`UART/CAN/1553B/AD/OC`,非法协议直接跳过;
- 外设名称为空的行直接跳过。
5. **数据类型转换**:自动将十六进制地址转整数、端口/缓存大小转整数,转换失败友好提示并赋默认值。
6. **日志记录**:全程打印解析日志(成功/失败/警告),便于问题排查。
### 配套Excel配置文件规范
1. 保存为**xlsx格式**兼容openpyxl解析xls格式需额外安装xlrd
2. 表头至少包含:**名称、协议类型、内存基地址**,其他列可选;
3. 协议类型填写:`UART/CAN/1553B/AD/OC`(大小写均可);
4. 地址填写:十六进制格式(如`0x20800000`、`0x20800000`、`0x0000`
5. 端口/缓存大小填写**纯数字**。
### 简单测试
在`config_files/`目录下创建`device_config.xlsx`,填入如下测试数据,直接运行该文件即可看到解析结果:
| 名称 | 协议类型 | 内存基地址 | 内存偏移地址 | UDP端口 | 说明 | 发送缓存区大小 | 接收缓存区大小 |
|--------|----------|------------|--------------|---------|------------|----------------|----------------|
| Uart0 | UART | 0x20800000 | 0x0000 | 8880 | 光纤陀螺A | 512 | 512 |
| CAN1 | CAN | 0x20810000 | 0x0100 | 8881 | 姿态传感器 | 1024 | 1024 |
| 1553B0 | 1553B | 0x20820000 | 0x0200 | 8882 | 测控模块 | 2048 | 2048 |
'''
import openpyxl
from loguru import logger
import re
import pandas as pd
import sys
import io
# 设置标准输出和标准错误的编码为UTF-8
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
def hex_to_int (hex_str):
"""处理十六进制字符串转整数(兼容带逗号 / 空格的格式,如 0x20800000、0x0000 1234:param hex_str: 十六进制字符串:return: 转换后的整数,失败返回 0"""
if not isinstance (hex_str, str):
return 0 #移除逗号、空格等非十六进制字符
clean_hex = re.sub (r'[,\s]', '', hex_str.strip ())
try:
return int (clean_hex, 16) if clean_hex else 0
except (ValueError, TypeError):
logger.warning (f"十六进制转换失败:{hex_str},默认返回 0")
return 0
'''
"""加载 Excel 外设配置文件,解析核心配置项
:param file_path: Excel ,csv文件路径xlsx/xls/csv
:return: 外设配置字典key = 外设名称value = 配置项字典
错误处理: 配置文件不存在,配置文件解析失败
"""
'''
def load_csv_config (file_path):
device_config = {}
try:
#打开 Excel 文件,只读取第一个工作表
# 先用 pandas 读取 CSV
df = pd.read_csv(file_path)
# 保存为 Excel
df.to_excel('file.xlsx', index=False)
# 然后使用 openpyxl 读取
wb = openpyxl.load_workbook('file.xlsx', data_only=True)
#wb = openpyxl.load_workbook(file_path, data_only=True)
ws = wb.active
logger.info(f"成功打开 Excel 配置文件:{file_path},工作表:{ws.title}")
#获取表头行,匹配核心配置列(兼容列名大小写 / 空格)
header = [cell.value.strip () if cell.value else "" for cell in ws [1]]
col_mapping = {"名称": header.index ([h for h in header if h.lower () == "名称"][0]) if [h for h in header if h.lower () == "名称"] else -1,
"协议类型": header.index ([h for h in header if h.lower () in ["协议", "协议类型"]][0]) if [h for h in header if h.lower () in ["协议", "协议类型"]] else -1,
"内存基地址": header.index ([h for h in header if h.lower () in ["内存基地址", "基地址"]][0]) if [h for h in header if h.lower () in ["内存基地址", "基地址"]] else -1,
"内存偏移地址": header.index ([h for h in header if h.lower () in ["内存偏移地址", "偏移地址"]][0]) if [h for h in header if h.lower () in ["内存偏移地址", "偏移地址"]] else -1,
"UDP端口": header.index ([h for h in header if h.lower () in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]][0]) if [h for h in header if h.lower () in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]] else -1,
"说明": header.index ([h for h in header if h.lower () == "说明"][0]) if [h for h in header if h.lower () == "说明"] else -1,
"发送缓存区大小": header.index ([h for h in header if h.lower () in ["发送缓存区大小", "发送缓存"]][0]) if [h for h in header if h.lower () in ["发送缓存区大小", "发送缓存"]] else -1,
"接收缓存区大小": header.index ([h for h in header if h.lower () in ["接收缓存区大小", "接收缓存"]][0]) if [h for h in header if h.lower () in ["接收缓存区大小", "接收缓存"]] else -1,
}
# 校验核心列(名称、协议、基地址必须存在)
required_cols = ["名称", "协议类型", "内存基地址"]
for col in required_cols:
if col_mapping [col] == -1:
logger.error (f"Excel 配置文件缺少核心列:{col},请检查表头")
# wb.close ()
return device_config
#遍历数据行(从第 2 行开始)
for row_num, row in enumerate (ws.iter_rows (min_row=2, values_only=True), start=2):
if not row [col_mapping ["名称"]]: # 外设名称为空则跳过
logger.warning (f"{row_num} 行:外设名称为空,跳过该行")
continue
dev_name = row [col_mapping ["名称"]].strip ()
#解析单行配置,缺省项赋默认值
dev_info = {"protocol": row [col_mapping ["协议类型"]].strip ().upper () if row [col_mapping ["协议类型"]] else "",
"base_addr": hex_to_int (row [col_mapping ["内存基地址"]]),
"offset_addr": hex_to_int (row [col_mapping ["内存偏移地址"]]),
"udp_port": int (row [col_mapping ["UDP端口"]]) if (row [col_mapping ["UDP端口"]] and str (row [col_mapping ["UDP端口"]]).isdigit ()) else 8888,
"desc": row [col_mapping ["说明"]].strip () if row [col_mapping ["说明"]] else "",
"send_cache": int (row [col_mapping ["发送缓存区大小"]]) if (row [col_mapping ["发送缓存区大小"]] and str (row [col_mapping ["发送缓存区大小"]]).isdigit ()) else 512,
"recv_cache": int (row [col_mapping ["接收缓存区大小"]]) if (row [col_mapping ["接收缓存区大小"]] and str (row [col_mapping ["接收缓存区大小"]]).isdigit ()) else 512,
"enable": True # 默认启用该外设
}
#校验协议类型合法性
if dev_info ["protocol"] not in ["UART", "CAN", "1553B", "AD", "OC"]:
logger.warning (f"{row_num} 行 [{dev_name}]:协议类型 {dev_info ['protocol']} 不合法,仅支持 UART/CAN/1553B/AD/OC跳过该行")
continue
device_config [dev_name] = dev_info
logger.debug (f"解析外设配置:{dev_name} | {dev_info ['protocol']} | 基地址 {hex (dev_info ['base_addr'])} | 端口号 {dev_info ['udp_port']} | 发送缓存 {dev_info ['send_cache']} | 接收缓存 {dev_info ['recv_cache']} | 说明 {dev_info ['desc']}")
wb.close()
logger.info(f"Excel 配置文件解析完成,共加载 {len (device_config)} 个有效外设配置")
print(f"Excel 配置文件解析完成,共加载 {len (device_config)} 个有效外设配置")
except FileNotFoundError:
logger.error (f"Excel 配置文件不存在:{file_path}")
print(f"Excel 配置文件不存在:{file_path}")
except Exception as e:
logger.error (f"Excel 配置文件解析失败:{str (e)}", exc_info=True)
print(f"Excel 配置文件解析失败:{str (e)}", exc_info=True)
return device_config
#测试代码(单独运行该文件时执行)
# if name == "main":
# test_config = load_excel_config("../../config_files/device_config.xlsx")
# print(test_config)
# 测试代码(单独运行该文件时执行)
if __name__ == "__main__":
test_config = load_csv_config("../../config_files/device_config.csv") # 替换为实际文件路径
print(test_config)

View File

@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
'''
Excel 配置解析excel_config.py基于openpyxl实现 Excel 文件解析提取外设名称、UDP 端口、协议类型、基地址等信息存入DEVICE_CONFIG_DICT。
'''
import openpyxl
from loguru import logger
import re
import pandas as pd
import os
import sys
import io
# 设置标准输出和标准错误的编码为UTF-8
# sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
# sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
def hex_to_int (hex_str):
"""处理十六进制字符串转整数(兼容带逗号 / 空格的格式,如 0x20800000、0x0000 1234
:param hex_str: 十六进制字符串
:return: 转换后的整数,失败返回 0
"""
if not isinstance (hex_str, str):
return 0 # 非字符串直接返回0
# 移除逗号、空格等非十六进制字符
clean_hex = re.sub (r'[,\s]', '', hex_str.strip ())
try:
return int (clean_hex, 16) if clean_hex else 0
except (ValueError, TypeError):
logger.warning (f"十六进制转换失败:{hex_str},默认返回 0")
return 0
def load_csv_config (file_path):
"""加载 Excel/CSV 外设配置文件,解析核心配置项
:param file_path: Excel/CSV文件路径xlsx/xls/csv
:return: 外设配置字典key = 外设名称value = 配置项字典
"""
device_config = {}
wb = None
temp_excel = "file.xlsx"
is_temp_file = False # 标记是否生成了临时文件
try:
# 处理CSV文件转换为Excel后解析
if file_path.endswith('.csv'):
df = pd.read_csv(file_path)
df.to_excel(temp_excel, index=False)
file_path = temp_excel
is_temp_file = True # 标记临时文件已生成
# 打开工作簿兼容直接传入Excel文件的情况
wb = openpyxl.load_workbook(file_path, data_only=True)
ws = wb.active
logger.info(f"成功打开配置文件:{file_path},工作表:{ws.title}")
# 获取表头行,匹配核心配置列(兼容列名大小写 / 空格)
header = [cell.value.strip() if cell.value else "" for cell in ws[1]]
col_mapping = {
"名称": header.index([h for h in header if h.lower() == "名称"][0]) if [h for h in header if h.lower() == "名称"] else -1,
"编号": header.index([h for h in header if h.lower() == "编号"][0]) if [h for h in header if h.lower() == "编号"] else -1,
"协议类型": header.index([h for h in header if h.lower() in ["协议", "协议类型"]][0]) if [h for h in header if h.lower() in ["协议", "协议类型"]] else -1,
"内存基地址": header.index([h for h in header if h.lower() in ["内存基地址", "基地址"]][0]) if [h for h in header if h.lower() in ["内存基地址", "基地址"]] else -1,
"内存偏移地址": header.index([h for h in header if h.lower() in ["内存偏移地址", "偏移地址"]][0]) if [h for h in header if h.lower() in ["内存偏移地址", "偏移地址"]] else -1,
"UDP端口": header.index([h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]][0]) if [h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "UDP port","port"]] else -1,
"UDP远程端口": header.index([h for h in header if h.lower() in ["UDP远程端口", "远程端口地址", "远程udp 地址", "remote_port","remote port"]][0]) if [h for h in header if h.lower() in ["UDP端口", "端口地址", "udp 地址", "remote_port","remote port"]] else -1,
"TCP端口": header.index([h for h in header if h.lower() in ["TCP端口", "端口地址", "TCP 地址", "tcp_local_port"]][0]) if [h for h in header if h.lower() in ["TCP端口", "tcp_local_port"]] else -1,
"TCP总线端口": header.index([h for h in header if h.lower() in ["TCP远程端口", "远程端口地址", "远程tcp 地址", "tcp_bus_port"]][0]) if [h for h in header if h.lower() in ["TCP端口", "tcp_bus_port"]] else -1,
"说明": header.index([h for h in header if h.lower() == "说明"][0]) if [h for h in header if h.lower() == "说明"] else -1,
"发送缓存区大小": header.index([h for h in header if h.lower() in ["发送缓存区大小", "发送缓存"]][0]) if [h for h in header if h.lower() in ["发送缓存区大小", "发送缓存"]] else -1,
"接收缓存区大小": header.index([h for h in header if h.lower() in ["接收缓存区大小", "接收缓存"]][0]) if [h for h in header if h.lower() in ["接收缓存区大小", "接收缓存"]] else -1,
}
# 校验核心列(名称、协议、基地址必须存在)
required_cols = ["名称", "协议类型", "内存基地址"]
missing_cols = [col for col in required_cols if col_mapping[col] == -1]
if missing_cols:
logger.error(f"配置文件缺少核心列:{','.join(missing_cols)},请检查表头")
return device_config
# 关键优化将迭代器转为列表提前加载所有行数据避免后续关闭wb后访问
rows_data = list(ws.iter_rows(min_row=2, values_only=True))
# 遍历数据行(从第 2 行开始)
for row_num, row in enumerate(rows_data, start=2):
if not row[col_mapping["名称"]]: # 外设名称为空则跳过
logger.warning(f"{row_num} 行:外设名称为空,跳过该行")
continue
dev_name = row[col_mapping["名称"]].strip()
print(f'原始值:{row[col_mapping["编号"]]}')
print(f'类型:{type(row[col_mapping["编号"]])}')
print(str(row[col_mapping["编号"]]))
str_value = str(row[col_mapping["编号"]])
print(f'isdigit?:{str_value.isdigit()}')
# 解析单行配置,缺省项赋默认值
dev_info = {
"protocol": row[col_mapping["协议类型"]].strip().upper() if row[col_mapping["协议类型"]] else "",
"number": int(row[col_mapping["编号"]]) if (row[col_mapping["编号"]] is not None and str(row[col_mapping["编号"]]).isdigit()) else 99,#2026/4/15
"base_addr": hex_to_int(row[col_mapping["内存基地址"]]),
"offset_addr": hex_to_int(row[col_mapping["内存偏移地址"]]),
"udp_port": int(row[col_mapping["UDP端口"]]) if (row[col_mapping["UDP端口"]] and str(row[col_mapping["UDP端口"]]).isdigit()) else 8888,
"remote_port": int(row[col_mapping["UDP远程端口"]]) if (row[col_mapping["UDP远程端口"]] and str(row[col_mapping["UDP远程端口"]]).isdigit()) else 9999,
"tcp_local_port": int(row[col_mapping["TCP端口"]]) if (row[col_mapping["TCP端口"]] and str(row[col_mapping["TCP端口"]]).isdigit()) else 1,
"tcp_bus_port": int(row[col_mapping["TCP总线端口"]]) if (row[col_mapping["TCP总线端口"]] and str(row[col_mapping["TCP总线端口"]]).isdigit()) else 1,
"desc": row[col_mapping["说明"]].strip() if row[col_mapping["说明"]] else "",
"send_cache": int(row[col_mapping["发送缓存区大小"]]) if (row[col_mapping["发送缓存区大小"]] and str(row[col_mapping["发送缓存区大小"]]).isdigit()) else 512,
"recv_cache": int(row[col_mapping["接收缓存区大小"]]) if (row[col_mapping["接收缓存区大小"]] and str(row[col_mapping["接收缓存区大小"]]).isdigit()) else 512,
"enable": True # 默认启用该外设
}
# 校验协议类型合法性
if dev_info["protocol"] not in ["UART", "CAN", "1553B", "AD", "OC"]:
logger.warning(f"{row_num} 行 [{dev_name}]:协议类型 {dev_info['protocol']} 不合法,仅支持 UART/CAN/1553B/AD/OC跳过该行")
continue
device_config[dev_name] = dev_info
logger.debug(
f"解析外设配置:{dev_name} | {dev_info['protocol']} | 基地址 {hex(dev_info['base_addr'])} | "
f"端口号 {dev_info['udp_port']} | 发送缓存 {dev_info['send_cache']} | 接收缓存 {dev_info['recv_cache']} | 说明 {dev_info['desc']}"
)
logger.info(f"配置文件解析完成,共加载 {len(device_config)} 个有效外设配置")
print(f"配置文件解析完成,共加载 {len(device_config)} 个有效外设配置")
except FileNotFoundError:
logger.error(f"配置文件不存在:{file_path}")
print(f"配置文件不存在:{file_path}")
except Exception as e:
logger.error(f"配置文件解析失败:{str(e)}", exc_info=True)
print(f"配置文件解析失败:{str(e)}")
finally:
# 第一步:先关闭工作簿(确保所有数据已读取完成)
if wb:
try:
wb.close()
logger.debug("Excel工作簿已正常关闭")
except Exception as e:
logger.warning(f"关闭Excel工作簿失败{str(e)}")
# 第二步:删除临时文件(仅当生成了临时文件时执行)
if is_temp_file and os.path.exists(temp_excel):
try:
# 延迟删除(避免文件句柄未释放)
import time
time.sleep(0.1)
os.remove(temp_excel)
logger.debug("临时Excel文件已删除")
except Exception as e:
logger.warning(f"删除临时Excel文件失败{str(e)}")
return device_config
# 测试代码(单独运行该文件时执行)
if __name__ == "__main__":
test_config = load_csv_config("config_files/device_config.csv")
print(test_config)

23
midware/core/__init__.py Normal file
View File

@@ -0,0 +1,23 @@
# midware/core/__init__.py
"""
核心业务层:总线-UDP解耦、总线驱动、数据处理
"""
# from .bus_udp_decoupler import (
# start_all_threads,
# stop_all_threads,
# protocol_dispatch_queue,
# bus_queues,
# udp_send_queue
# )
from .bus_udp_decoupler import (
start_all_threads,
stop_all_threads,
bus_recv_queues,
bus_send_queues,
udp_send_queue,
udp_recv_dispatch_queue
)
# 暴露版本/作者等元信息(可选)
__version__ = "1.0.0"
__author__ = "xxx"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

View File

View File

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

View File

View File

View File

@@ -0,0 +1,637 @@
# -*- coding: utf-8 -*-
"""
Tan Mintgyan
2026/3/9
总线-UDP双向通信解耦模块新增带协议优先级1553B>CAN>UART>AD>OC>网络)
"""
import threading
import queue
import time
from loguru import logger
from typing import Dict, Any, Optional
from midware.config.base_config import DEVICE_CONFIG_DICT
from midware.drivers import create_bus_driver
import midware.config.base_config as base_config
from ..drivers.renode_agent import renode
# ========== 1. 核心:定义协议优先级映射(数值越小优先级越高) ==========
PROTOCOL_PRIORITY = {
"1553B": 1, # 最高优先级
"CAN": 2,
"UART": 3,
"AD": 4,
"OC": 5,
"网络": 6 # 最低优先级
}
MAX_QUEUE_SIZE = 1000 # 队列最大长度(可按协议单独调整)
# ========== 2. 收发队列分离发送队列统一用PriorityQueue ==========
# 总线接收队列(总线→中间件:上行数据,无需优先级)
bus_recv_queues = {
"1553B": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"CAN": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"UART": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"AD": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"OC": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"网络": queue.Queue(maxsize=MAX_QUEUE_SIZE)
}
# 总线发送队列(中间件→总线:下行数据,带优先级)
# 统一使用PriorityQueue按PROTOCOL_PRIORITY的数值排序
bus_send_queues = {
"1553B": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
"CAN": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
"UART": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
"AD": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
"OC": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
"网络": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE)
}
# UDP发送队列中间件→UDP上行数据
udp_send_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
# UDP接收分发队列UDP→中间件下行数据
udp_recv_dispatch_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
# ========== 3. UDP接收线程自动映射协议优先级 ==========
def udp_recv_thread(udp_server):
"""UDP接收线程接收下行指令自动分配优先级后放入总线发送队列"""
logger.info("UDP接收线程下行指令启动")
##测试用
try:
logger.debug("测试队列入队")
print("测试队列入队")
protocol= 'AD'
dev_name = 'adc1',#2026/4/9
#dev_name = '正电压采集热敏量采集',
raw_data = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'#56*2=112字节
priority = 3
bus_send_queues[protocol].put_nowait((
priority, # 按数值从小到大优先
{
"dev_name": dev_name,
"raw_data": raw_data,
"protocol": protocol,
"priority": priority,
"recv_time": time.time() # 记录接收时间
}
))
logger.debug(
f"下行指令入队|{protocol}(优先级{priority}{dev_name}"
f"队列长度:{bus_send_queues[protocol].qsize()}"
)
except queue.Full:
logger.warning(
f"{protocol}(优先级{priority})发送队列已满,丢弃 {dev_name} 下行指令"
)
while True:
try:
recv_data = udp_server.recv_multi_udp_hex()
if recv_data:
for data in recv_data:
protocol = data.get("protocol", "").strip().upper()
dev_name = data.get("dev_name", "")
raw_data = data.get("raw_data", b"")
recv_time = time.time()
# 核心自动获取协议优先级未知协议设为最低优先级7
priority = PROTOCOL_PRIORITY.get(protocol, 7)
if protocol not in bus_send_queues:
logger.warning(f"未知协议 {protocol},丢弃下行指令:{dev_name}(优先级:{priority}")
continue
# 放入总线发送队列PriorityQueue格式(优先级数值, 数据)
try:
bus_send_queues[protocol].put_nowait((
priority, # 按数值从小到大优先
#recv_time,#新增,用来打破优先级相同的平局
{
"dev_name": dev_name,
"raw_data": raw_data,
"protocol": protocol,
"priority": priority,
"recv_time": recv_time # 记录接收时间
}
))
logger.debug(
f"下行指令入队|{protocol}(优先级{priority}{dev_name}"
f"队列长度:{bus_send_queues[protocol].qsize()}"
)
except queue.Full:
logger.warning(
f"{protocol}(优先级{priority})发送队列已满,丢弃 {dev_name} 下行指令"
)
time.sleep(1e-6)
except Exception as e:
logger.error(f"UDP接收线程异常{str(e)}", exc_info=True)
time.sleep(0.1)
# ========== 4. 总线发送线程:按优先级消费数据 ==========
'''
def bus_send_thread(protocol: str, bus_driver: Optional[Any] = None):#2026/3/9停用
"""总线发送线程:优先消费高优先级数据(数值越小越先处理)"""
logger.info(f"{protocol} 总线发送线程启动(优先级:{PROTOCOL_PRIORITY.get(protocol, 7)}")
while True:
try:
# PriorityQueue自动按优先级取值数值小的先出
priority, send_data = bus_send_queues[protocol].get(timeout=1)
dev_name = send_data["dev_name"]
raw_data = send_data["raw_data"]
recv_time = send_data["recv_time"]
# 超时检查可选超过5秒未发送则丢弃
if time.time() - recv_time > 5:
logger.warning(
f"{protocol}(优先级{priority}{dev_name} 指令超时5秒丢弃"
)
bus_send_queues[protocol].task_done()
continue
# 核心:总线发送逻辑(替换为真实驱动调用)
logger.info(
f"总线发送|{protocol}(优先级{priority}{dev_name}"
f"数据:{raw_data.hex()}|队列剩余:{bus_send_queues[protocol].qsize()}"
)
# if bus_driver:
# bus_driver.send(raw_data) # 调用总线驱动发送
# 标记任务完成
bus_send_queues[protocol].task_done()
except queue.Empty:
continue
except Exception as e:
logger.error(f"{protocol} 发送线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
'''
# ========== 修正:总线发送线程(参数改为接收驱动配置) ==========
def bus_send_thread(protocol: str, bus_driver_config: Optional[Dict[str, Any]] = None):
"""
总线发送线程:通过统一驱动接口发送数据
:param protocol: 协议名称
:param bus_driver_config: 驱动配置字典
"""
logger.info(f"{protocol} 总线发送线程启动(优先级:{PROTOCOL_PRIORITY.get(protocol, 7)}")
# 创建驱动实例
try:
driver = create_bus_driver(protocol, config=bus_driver_config)
# 连接总线
if not driver.connect():
logger.error(f"{protocol} 驱动连接失败,线程退出")
return
except Exception as e:
logger.error(f"{protocol} 驱动创建失败:{str(e)},线程退出")
return
while True:
try:
priority, send_data = bus_send_queues[protocol].get(timeout=1)
dev_name = send_data["dev_name"]
raw_data = send_data["raw_data"]
recv_time = send_data["recv_time"]
'''# 超时检查
if time.time() - recv_time > 5:
logger.warning(f"{protocol}(优先级{priority}{dev_name} 指令超时,丢弃")
bus_send_queues[protocol].task_done()
continue
'''
# 调用驱动发送(统一接口)
send_success = driver.send(
data=raw_data,
priority=priority, # 传递优先级参数
dev_name=dev_name # 传递设备名
)
if send_success:
logger.info(
f"{protocol}(优先级{priority}{dev_name} 发送成功|"
f"数据:{raw_data.hex()}|队列剩余:{bus_send_queues[protocol].qsize()}"
)
else:
logger.error(f"{protocol}(优先级{priority}{dev_name} 发送失败")
bus_send_queues[protocol].task_done()
except queue.Empty:
continue
except Exception as e:
logger.error(f"{protocol} 发送线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
# 线程退出时断开驱动(析构函数也会自动处理)
driver.disconnect()
# ========== 修正start_all_threads 函数(适配 bus_driver_configs 参数) ==========
def start_all_threads(udp_server, bus_driver_configs: Optional[Dict[str, Any]] = None):
"""
启动所有线程
:param udp_server: UDP服务实例
:param bus_driver_configs: 驱动配置字典,格式:{"1553B": {...}, "CAN": {...}, ...}
"""
threads = []
bus_driver_configs = bus_driver_configs or {} # 无配置时设为空字典
# 按优先级从高到低排序协议
sorted_protos = sorted(PROTOCOL_PRIORITY.keys(), key=lambda x: PROTOCOL_PRIORITY[x])
# 1. UDP接收线程
udp_recv_t = threading.Thread(target=udp_recv_thread, args=(udp_server,), daemon=True)
threads.append(udp_recv_t)
# 2. 总线发送线程(按优先级排序启动,传递对应驱动配置)
for protocol in sorted_protos:
# 获取当前协议的驱动配置
driver_config = bus_driver_configs.get(protocol, {})
send_t = threading.Thread(
target=bus_send_thread,
args=(protocol, driver_config), # 传递协议名+驱动配置
daemon=True
)
threads.append(send_t)
'''
# 3. 总线接收线程(按优先级排序启动,传递对应驱动配置)
for protocol in sorted_protos:
driver_config = bus_driver_configs.get(protocol, {})
recv_t = threading.Thread(
target=bus_recv_thread,
args=(protocol, driver_config), # 传递协议名+驱动配置
daemon=True
)
threads.append(recv_t)
'''
# 4. 总线接收分发线程
recv_dispatch_t = threading.Thread(target=bus_recv_dispatch_thread, daemon=True)
threads.append(recv_dispatch_t)
# 5. UDP发送线程
udp_send_t = threading.Thread(target=udp_send_thread, args=(udp_server,), daemon=True)
threads.append(udp_send_t)
#6. 总线轮询线程
for protocol in sorted_protos:
driver_config = bus_driver_configs.get(protocol, {})
daq_t = threading.Thread(
target=bus_daq_thread,
args=(protocol, driver_config), # 传递协议名+驱动配置
daemon=True
)
threads.append(daq_t)
# 启动所有线程
for t in threads:
t.start()
logger.info(f"线程 {t.name} 启动成功")
return threads
# ========== 其他函数stop_all_threads、udp_recv_thread等保持不变 ==========
# ========== 5. 总线接收线程(无优先级,保持原有逻辑) ==========
# ========== 修正:总线接收线程(参数改为接收驱动配置) ==========
def bus_recv_thread(protocol: str, bus_driver_config: Optional[Dict[str, Any]] = None):
"""总线接收线程:通过统一驱动接口接收数据"""
logger.info(f"{protocol} 总线接收线程启动")
# 创建驱动实例
try:
driver = create_bus_driver(protocol, config=bus_driver_config)
if not driver.connect():
logger.error(f"{protocol} 驱动连接失败,线程退出")
return
except Exception as e:
logger.error(f"{protocol} 驱动创建失败:{str(e)},线程退出")
return
time.sleep(0.001)#5->0.5
while True:
try:
'''
##循环发送读取请求
# if(protocol=="UART"):
# sysbus_cmd = f'uart24 GetTXFIFODataString'
# print(sysbus_cmd)
# response = renode.send_sync(sysbus_cmd)#2026/4/10
time.sleep(0.001)
##循环发送读取请求
if(protocol == "UART"):#发现protocol == "CAN"时候循环更快
sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
logger.info(f'uart0轮询请求{sysbus_cmd}')#print(sysbus_cmd)
response = renode.send_sync(sysbus_cmd,timeout_ms=10)#2026/4/10
time.sleep(0.001)
if(protocol == "CAN"):
sysbus_cmd = f'can_a GetTxBufferDataString'#CAN_A
#sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
logger.info(f'can_a轮询请求{sysbus_cmd}')#print(sysbus_cmd)
response = renode.send_sync(sysbus_cmd,timeout_ms=10)#2026/4/10
time.sleep(0.25)
'''
# 调用驱动接收(统一接口)
# raw_data = driver.recv(timeout=0.001) # 短超时,非阻塞
## 以下代码没有作用因为recv接口没有实现
'''
if raw_data and len(raw_data) > 0:
dev_name = f"{protocol}_DEV"
try:
bus_recv_queues[protocol].put_nowait({
"dev_name": dev_name,
"raw_data": raw_data,
"protocol": protocol,
"recv_time": time.time()
})
logger.debug(f"{protocol} 接收:{dev_name} 上行数据,队列长度:{bus_recv_queues[protocol].qsize()}")
except queue.Full:
logger.warning(f"{protocol} 接收队列已满,丢弃上行数据")
time.sleep(1e-6)
'''
except Exception as e:
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
driver.disconnect()
'''
def bus_recv_thread(protocol: str, bus_driver: Optional[Any] = None):#2026/3/9 停用
"""总线接收线程:接收上行数据,放入接收队列"""
logger.info(f"{protocol} 总线接收线程启动")
while True:
try:
# 模拟总线接收替换为真实驱动的recv接口
# raw_data = bus_driver.recv()
raw_data = b"" # 占位
if raw_data:
dev_name = f"{protocol}_DEV"
try:
bus_recv_queues[protocol].put_nowait({
"dev_name": dev_name,
"raw_data": raw_data,
"protocol": protocol,
"recv_time": time.time()
})
logger.debug(
f"总线接收|{protocol}{dev_name}"
f"队列长度:{bus_recv_queues[protocol].qsize()}"
)
except queue.Full:
logger.warning(f"{protocol} 接收队列已满,丢弃上行数据")
time.sleep(1e-6)
except Exception as e:
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
'''
# ========== 5.5 总线轮询线程(无优先级,保持原有逻辑) ==========
def bus_daq_thread(protocol: str, bus_driver_config: Optional[Dict[str, Any]] = None):
"""总线接收线程:通过统一驱动接口接收数据"""
logger.info(f"{protocol} 总线接收线程启动")
# 创建驱动实例
try:
driver = create_bus_driver(protocol, config=bus_driver_config)
if not driver.connect():
logger.error(f"{protocol} 驱动连接失败,线程退出")
return
except Exception as e:
logger.error(f"{protocol} 驱动创建失败:{str(e)},线程退出")
return
time.sleep(0.001)#5->0.5
while True:
try:
##循环发送读取请求
# if(protocol=="UART"):
# sysbus_cmd = f'uart24 GetTXFIFODataString'
# print(sysbus_cmd)
# response = renode.send_sync(sysbus_cmd)#2026/4/10
time.sleep(0.001)
##循环发送读取请求
if(protocol == "CAN"):#发现protocol == "CAN"时候循环更快
sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
logger.info(f'uart0轮询请求{sysbus_cmd}')#print(sysbus_cmd)
response = renode.send_sync(sysbus_cmd,timeout_ms=5)#2026/4/10
# response = renode.enqueue_cmd(sysbus_cmd)#2026/4/10
time.sleep(0.001)
sysbus_cmd = f'uart18 GetTXFIFODataString'#uart 0
logger.info(f'uart18 轮询请求:{sysbus_cmd}')#print(sysbus_cmd)
response = renode.send_sync(sysbus_cmd,timeout_ms=5)#2026/4/10
# response = renode.enqueue_cmd(sysbus_cmd)#2026/4/10
time.sleep(0.001)
if(protocol == "CAN"):
sysbus_cmd = f'can_a GetTxBufferDataString'#CAN_A
#sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
logger.info(f'can_a轮询请求{sysbus_cmd}')#print(sysbus_cmd)
response = renode.send_sync(sysbus_cmd,timeout_ms=5)#2026/4/10
#response = renode.enqueue_cmd(sysbus_cmd)#2026/4/10
time.sleep(0.25)
# 调用驱动接收(统一接口)
raw_data = driver.recv(timeout=0.001) # 短超时,非阻塞
except Exception as e:
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
driver.disconnect()
import time
'''
# ========== 5.5 总线轮询线程(无优先级,保持原有逻辑) ==========
def bus_daq_thread(protocol: str, bus_driver_config: Optional[Dict[str, Any]] = None):
logger.info(f"{protocol}总线轮询线程启动")
# 创建驱动实例
try:
driver = create_bus_driver(protocol, config=bus_driver_config)
if not driver.connect():
logger.error(f"{protocol} 驱动连接失败,线程退出")
return
except Exception as e:
logger.error(f"{protocol} 驱动创建失败:{str(e)},线程退出")
return
time.sleep(5)
# ==================原来的while循环删除==============#
poll_period = 0.25
next_run_time = time.perf_counter()
while True:
try:
if protocol == "UART":
sysbus_cmd = f'uart0 GetTXFIFODataString'#uart 0
logger.info(f'uart0轮询请求{sysbus_cmd}')#print(sysbus_cmd)
response = renode.send_sync(sysbus_cmd,timeout_ms=1)#2026/4/10
elif protocol =="CAN":
sysbus_cmd = f'can_a GetTxBufferDataString'#CAN_A
logger.info(f'can_a轮询请求{sysbus_cmd}')#print(sysbus_cmd)
response = renode.send_sync(sysbus_cmd,timeout_ms=1)#2026/4/10
next_run_time = poll_period + next_run_time
sleep_time = next_run_time- time.perf_counter()
if sleep_time >0:
time.sleep(sleep_time)
#如果小于0代表超时
except Exception as e:
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
time.sleep(0.01)
'''
# ========== 6. 总线接收分发线程(无优先级,保持原有逻辑) ==========
def bus_recv_dispatch_thread():
"""汇总总线接收数据转发到UDP发送队列"""
logger.info("总线接收分发线程启动")
while True:
try:
for protocol, q in bus_recv_queues.items():
try:
recv_data = q.get_nowait()
try:
udp_send_queue.put_nowait(recv_data)
logger.debug(
f"上行数据转发到udp_send_queue{protocol}{recv_data['dev_name']}{recv_data['raw_data'].hex()}"
f"UDP队列长度{udp_send_queue.qsize()}"
)
except queue.Full:
logger.warning(f"UDP发送队列已满丢弃 {protocol} 上行数据")
q.task_done()
except queue.Empty:
continue
time.sleep(1e-6)
except Exception as e:
logger.error(f"总线接收分发线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
'''
# ========== 7. UDP发送线程无优先级保持原有逻辑 ==========
def udp_send_thread(udp_server): #2026/4/15停用因为本函数智能输出一个UDP端口
"""UDP发送线程发送上行数据到故障注入平台"""
logger.info("UDP发送线程上行数据启动")
while True:
try:
send_data = udp_send_queue.get(timeout=1)
dev_name = send_data["dev_name"]
raw_data = send_data["raw_data"]
udp_server.send_to_fault(raw_data, dev_name)
logger.debug(
f"UDP发送{dev_name}|数据:{raw_data.hex()}"
f"队列剩余:{udp_send_queue.qsize()}"
)
udp_send_queue.task_done()
except queue.Empty:
continue
except Exception as e:
logger.error(f"UDP发送线程异常{str(e)}", exc_info=True)
time.sleep(0.1)
'''
# ========== 7. UDP发送线程已修改按设备send_port动态发送 ==========2026/4/15
def udp_send_thread(udp_server):
"""UDP发送线程按设备配置的send_port发送到故障注入平台"""
logger.info("UDP发送线程上行数据启动")
while True:
try:
send_data = udp_send_queue.get(timeout=1)
dev_name = send_data["dev_name"]
raw_data = send_data["raw_data"]
protocol = send_data["protocol"]
# ======================
# 🔥 关键:从设备配置获取远端端口 send_port
# ======================
send_port = 9999 # 默认端口
if dev_name in base_config.DEVICE_CONFIG_DICT: ##引用全局变量时候有问题,这里需要修改
dev_cfg = base_config.DEVICE_CONFIG_DICT[dev_name]
send_port = dev_cfg.get("remote_port", 9999) # 读取CSV里的 remote_port
# ======================
# 🔥 发送到指定远端端口(修改这里)
# ======================
udp_server.send_to_fault(raw_data, dev_name, send_port)
logger.info(
f"通过UDP向动力学或前端发送{dev_name}port:{send_port}|数据:{raw_data.hex()}" #防止刷屏,先注释
)
udp_send_queue.task_done()
except queue.Empty:
continue
except Exception as e:
logger.error(f"UDP发送线程异常{str(e)}", exc_info=True)
time.sleep(0.1)
'''
# ========== 8. 线程管理(启动/停止) ==========
def start_all_threads(udp_server, bus_drivers: Dict[str, Any] = None):#2026/3/9停用
"""启动所有线程(按优先级顺序启动,非必须,仅为日志清晰)"""
threads = []
bus_drivers = bus_drivers or {}
# 按优先级从高到低启动总线发送线程(日志更清晰)
sorted_protos = sorted(PROTOCOL_PRIORITY.keys(), key=lambda x: PROTOCOL_PRIORITY[x])
# 1. UDP接收线程
udp_recv_t = threading.Thread(target=udp_recv_thread, args=(udp_server,), daemon=True)
threads.append(udp_recv_t)
# 2. 总线发送线程(按优先级排序启动)
for protocol in sorted_protos:
driver = bus_drivers.get(protocol)
send_t = threading.Thread(target=bus_send_thread, args=(protocol, driver), daemon=True)
threads.append(send_t)
# 3. 总线接收线程(按优先级排序启动)
for protocol in sorted_protos:
driver = bus_drivers.get(protocol)
recv_t = threading.Thread(target=bus_recv_thread, args=(protocol, driver), daemon=True)
threads.append(recv_t)
# 4. 总线接收分发线程
recv_dispatch_t = threading.Thread(target=bus_recv_dispatch_thread, daemon=True)
threads.append(recv_dispatch_t)
# 5. UDP发送线程
udp_send_t = threading.Thread(target=udp_send_thread, args=(udp_server,), daemon=True)
threads.append(udp_send_t)
# 启动所有线程
for t in threads:
t.start()
logger.info(f"线程 {t.name} 启动成功")
return threads
'''
def stop_all_threads():
"""优雅停止所有线程"""
logger.info("开始停止所有线程,等待队列处理完成...")
# 等待发送队列(按优先级从高到低)
sorted_protos = sorted(PROTOCOL_PRIORITY.keys(), key=lambda x: PROTOCOL_PRIORITY[x])
for protocol in sorted_protos:
bus_send_queues[protocol].join()
# 等待接收队列
for protocol in sorted_protos:
bus_recv_queues[protocol].join()
# 等待UDP发送队列
udp_send_queue.join()
logger.info("所有队列处理完成,线程已停止")
# ========== 扩展函数:动态调整优先级(可选) ==========
def update_protocol_priority(protocol: str, new_priority: int):
"""
动态调整协议优先级(运行时生效)
:param protocol: 协议名称(如"1553B"
:param new_priority: 新优先级数值(越小越高)
"""
if protocol in PROTOCOL_PRIORITY:
old_priority = PROTOCOL_PRIORITY[protocol]
PROTOCOL_PRIORITY[protocol] = new_priority
logger.info(f"协议 {protocol} 优先级调整:{old_priority}{new_priority}")
else:
logger.warning(f"未知协议 {protocol},无法调整优先级")

View File

View File

@@ -0,0 +1,97 @@
import crcmod
from loguru import logger
import os
#初始化校验和算法,适配不同总线协议(通用 CRC16 为主,兼容自定义校验)
#预定义各总线校验算法,可根据需求扩展
#CRC16_UART = crcmod.predefined.Crc('crc16_modbus')
CRC16_UART = crcmod.predefined.Crc('crc16')
#CRC16_CAN = crcmod.predefined.Crc('crc16/ccitt')
CRC16_CAN = crcmod.predefined.Crc('crc16')
#CRC16_1553B = crcmod.predefined.Crc('crc16_x25')
CRC16_1553B = crcmod.predefined.Crc('crc16')
#确保日志目录存在
if not os.path.exists("logs"):os.makedirs("logs")
def _calculate_checksum (data: bytes, protocol: str) -> bytes:
"""私有方法:根据协议类型计算校验和
:param data: 原始二进制数据
:param protocol: 总线协议UART/CAN/1553B/AD/OC
:return: 校验和字节流2 字节,大端)
"""
if not isinstance (data, bytes) or len (data) == 0:
return b'\x00\x00'
crc = None
if protocol == "UART":
crc = CRC16_UART
elif protocol == "CAN":
crc = CRC16_CAN
elif protocol == "1553B":
crc = CRC16_1553B
elif protocol in ["AD", "OC"]:
#AD/OC 采用简单累加和校验(低字节),适配星务平台常用规则
sum_val = sum(byte for byte in data) & 0xFF
return bytes([sum_val])
else:
#未知协议默认使用 CRC16_MODBUS
crc = CRC16_UART
#重置 CRC 计算器并计算
#crc.reset()
crc.update(data)
#返回 2 字节大端校验和
return crc.crcValue.to_bytes(2, byteorder='big')
def check_checksum (data: bytes, protocol: str, device_name: str) -> bool:
"""核心校验和检测逻辑分离数据与校验位对比计算值与传入值规则数据末尾携带校验位不同协议校验位长度不同UART/CAN/1553B 为 2 字节AD/OC 为 1 字节)
:param data: 包含校验位的原始二进制数据
:param protocol: 总线协议UART/CAN/1553B/AD/OC
:param device_name: 外设名称(用于日志定位)
:return: 校验通过返回 True失败返回 False 并记录错误日志
"""
#基础参数校验
if not isinstance (data, bytes) or len (data) == 0:
logger.error (f"{device_name}-{protocol}】校验和检测失败:输入数据为空或非二进制格式")
return False
if protocol not in ["UART", "CAN", "1553B", "AD", "OC"]:
logger.error (f"{device_name}-{protocol}】校验和检测失败:不支持的协议类型")
return False
#定义各协议校验位长度
checksum_len = 2 if protocol in ["UART", "CAN", "1553B"] else 1
#数据长度需大于校验位长度,否则无效
if len (data) <= checksum_len:
logger.error (f"{device_name}-{protocol}】校验和检测失败:数据长度 {len (data)} 字节,小于校验位长度 {checksum_len} 字节")
return False
#分离原始数据和携带的校验位
raw_data = data[:-checksum_len]
received_checksum = data[-checksum_len:]
#计算原始数据的校验和
calculated_checksum = _calculate_checksum(raw_data, protocol)
#对比校验和
if received_checksum == calculated_checksum:
logger.debug (f"{device_name}-{protocol}】校验和检测通过:接收校验位 {received_checksum.hex ()},计算校验位 {calculated_checksum.hex ()}")
return True
else:
#校验失败,记录详细错误日志(含设备、协议、数据、校验位对比)
error_msg = (f"{device_name}-{protocol}】校验和检测失败 |"f"原始数据长度:{len (raw_data)} 字节 |"f"接收校验位:{received_checksum.hex ()} |"f"计算校验位:{calculated_checksum.hex ()} |"f"原始数据(前 16 字节):{raw_data [:16].hex () if len (raw_data)>=16 else raw_data.hex ()}")
logger.error (error_msg)
return False
def verify_frame_integrity (data: bytes, protocol: str, device_name: str) -> bool:
"""扩展方法:帧完整性检测(可选),结合校验和 + 协议帧长度规则用于 CAN单帧≤11 字节、1553B 等有帧长限制的协议
:param data: 包含校验位的原始二进制数据
:return: 完整性通过返回 True否则 False"""
#先执行校验和检测
if not check_checksum(data, protocol, device_name):
return False
#各协议帧长度规则校验
frame_rules = {"CAN": lambda d: len (d) - 2 <= 11, # CAN 单帧≤11 字节(扣除 2 字节校验位)"1553B": lambda d: len (d) % 2 == 0, # 1553B 数据为 2 字节整数倍(军用总线规范)"UART": lambda d: True, # UART 不定长,无帧长限制"AD": lambda d: True, # AD 无帧长限制"OC": lambda d: True # OC 无帧长限制
}
if protocol in frame_rules:
if not frame_rulesprotocol:
error_msg = f"{device_name}-{protocol}】帧完整性检测失败:数据长度不符合协议规范,数据总长度 {len (data)} 字节"
logger.error (error_msg)
return False
logger.debug (f"{device_name}-{protocol}】帧完整性检测通过")
return True

View File

@@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
"""
Created on Tan Mingyan 2026/3/6
四、关键设计亮点(解耦 + 队列的核心价值)
彻底解耦:
UDP 收发 ≠ 总线处理UDP 线程只做「数据搬运」,总线线程只做「业务处理」,修改总线逻辑无需改动 UDP 代码;
异常隔离:某一线程(如 CAN 处理)崩溃,不影响 UDP 收发和其他总线线程运行。
队列缓冲区的核心作用:
削峰填谷UDP 突发大量数据时,队列暂存,总线线程按能力消费,避免数据丢失;
异步通信:线程间通过队列通信,无直接调用,消除阻塞依赖;
可监控:通过 qsize() 监控队列长度,及时发现「生产 > 消费」的瓶颈(如队列持续满则需优化总线处理速度)。
工业级容错:
队列满时丢弃数据 + 告警,避免内存溢出;
线程异常时休眠重试,避免 CPU 100%
守护线程daemon=True主线程退出时自动终止子线程避免僵尸线程
优雅退出join() 等待队列处理完成,避免强制退出导致数据丢失。
五、扩展建议(根据业务需求调整)
队列持久化:若需断电不丢数据,可将队列数据写入本地文件 / Redis
优先级队列:对关键设备(如 1553B使用 queue.PriorityQueue优先处理高优先级数据
流量控制:监控队列长度,超过阈值时暂停 UDP 接收(或降低接收频率);
线程池优化:若总线协议过多,改用 concurrent.futures.ThreadPoolExecutor 管理线程,避免线程数量过多。
"""
"""
总线-UDP解耦核心模块队列+多线程
"""
import threading
import queue
import time
from loguru import logger
from typing import Dict, Any, List
# ========== 1. 全局队列定义(按协议分类) ==========
# 队列最大长度避免内存溢出根据业务调整如1000
MAX_QUEUE_SIZE = 1000
# 协议分发队列UDP接收→协议分流
protocol_dispatch_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
# 各总线处理队列
bus_queues = {
"UART": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"CAN": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"1553B": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"AD": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"OC": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"网络": queue.Queue(maxsize=MAX_QUEUE_SIZE)
}
# UDP发送队列总线回传→UDP发送
udp_send_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
# ========== 2. UDP接收线程仅负责收数据不处理 ==========
def udp_receive_thread(udp_server):
"""UDP接收线程独立运行仅收数据放入分发队列"""
logger.info("UDP接收线程启动")
while True:
try:
recv_data = udp_server.recv_multi_udp_hex()
if recv_data:
for data in recv_data:
# 非阻塞放入队列,避免线程阻塞
try:
protocol_dispatch_queue.put_nowait(data)
logger.debug(f"UDP接收{data['dev_name']} 数据放入分发队列,队列当前长度:{protocol_dispatch_queue.qsize()}")
except queue.Full:
logger.warning(f"协议分发队列已满,丢弃 {data['dev_name']} 数据")
time.sleep(1e-6)
except Exception as e:
logger.error(f"UDP接收线程异常{str(e)}", exc_info=True)
time.sleep(0.1) # 异常时休眠避免CPU飙高
# ========== 3. 协议分发线程(仅负责分流,不处理业务) ==========
def protocol_dispatch_thread():
"""协议分发线程从分发队列取数据按protocol分流到对应总线队列"""
logger.info("协议分发线程启动")
while True:
try:
# 阻塞取数据队列为空时等待无CPU消耗
data = protocol_dispatch_queue.get(timeout=1)
protocol = data.get("protocol", "").strip().upper()
# 分流到对应总线队列
if protocol in bus_queues:
try:
bus_queues[protocol].put_nowait(data)
logger.debug(f"分发:{data['dev_name']} 数据到 {protocol} 队列,队列长度:{bus_queues[protocol].qsize()}")
except queue.Full:
logger.warning(f"{protocol} 队列已满,丢弃 {data['dev_name']} 数据")
else:
logger.warning(f"未知协议 {protocol},丢弃 {data['dev_name']} 数据")
# 标记任务完成队列join时需要
protocol_dispatch_queue.task_done()
except queue.Empty:
continue # 队列为空,继续循环
except Exception as e:
logger.error(f"协议分发线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
# ========== 4. 总线处理线程以UART为例其他协议同理 ==========
def bus_process_thread(protocol: str, udp_server):
"""通用总线处理线程从对应队列取数据处理后放入UDP发送队列"""
logger.info(f"{protocol} 总线处理线程启动")
while True:
try:
# 阻塞取数据
data = bus_queues[protocol].get(timeout=1)
dev_name = data["dev_name"]
raw_data = data["raw_data"]
# ========== 核心:总线业务处理(解耦后仅在此处修改) ==========
logger.info(f"{protocol} 处理:{dev_name} 原始数据:{raw_data.hex()}")
# 1. 总线数据解析如UART波特率解析、CAN帧解析等
processed_data = raw_data # 示例:实际需替换为业务逻辑
# 2. 总线数据发送如写入UART串口、发送CAN帧等
# bus_driver.send(protocol, processed_data) # 总线驱动调用
# ========== 处理完成后回传数据到UDP发送队列 ==========
try:
udp_send_queue.put_nowait({
"dev_name": dev_name,
"data": processed_data,
"protocol": protocol
})
logger.debug(f"{protocol} 处理完成,{dev_name} 回传数据放入UDP发送队列")
except queue.Full:
logger.warning(f"UDP发送队列已满丢弃 {dev_name} 回传数据")
# 标记任务完成
bus_queues[protocol].task_done()
except queue.Empty:
continue
except Exception as e:
logger.error(f"{protocol} 总线线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
# ========== 5. UDP发送线程仅负责发数据不处理 ==========
def udp_send_thread(udp_server):
"""UDP发送线程从发送队列取数据发送到故障注入平台"""
logger.info("UDP发送线程启动")
while True:
try:
# 阻塞取数据
send_data = udp_send_queue.get(timeout=1)
dev_name = send_data["dev_name"]
data = send_data["data"]
# 调用UDP服务发送
udp_server.send_to_fault(data, dev_name)
logger.debug(f"UDP发送{dev_name} 数据到故障注入平台,队列剩余:{udp_send_queue.qsize()}")
# 标记任务完成
udp_send_queue.task_done()
except queue.Empty:
continue
except Exception as e:
logger.error(f"UDP发送线程异常{str(e)}", exc_info=True)
time.sleep(0.1)
# ========== 6. 线程启动/停止管理集成到main.py ==========
'''
def start_all_threads(udp_server):
"""启动所有解耦线程"""
threads = []
# 1. UDP接收线程
udp_recv_t = threading.Thread(target=udp_receive_thread, args=(udp_server,), daemon=True)
threads.append(udp_recv_t)
# 2. 协议分发线程
dispatch_t = threading.Thread(target=protocol_dispatch_thread, daemon=True)
threads.append(dispatch_t)
# 3. 各总线处理线程
for protocol in bus_queues.keys():
bus_t = threading.Thread(target=bus_process_thread, args=(protocol, udp_server), daemon=True)
threads.append(bus_t)
# 4. UDP发送线程
udp_send_t = threading.Thread(target=udp_send_thread, args=(udp_server,), daemon=True)
threads.append(udp_send_t)
# 启动所有线程
for t in threads:
t.start()
logger.info(f"线程 {t.name} 启动成功")
return threads
def stop_all_threads():
"""优雅停止所有线程(等待队列处理完成)"""
logger.info("开始停止所有线程,等待队列处理完成...")
# 等待队列所有任务完成
protocol_dispatch_queue.join()
for q in bus_queues.values():
q.join()
udp_send_queue.join()
logger.info("所有队列处理完成,线程已停止")
'''

View File

@@ -0,0 +1,234 @@
# -*- coding: utf-8 -*-
"""
Created on Tan Mingyan 2026/3/7
"""
"""
总线-UDP双向通信解耦模块加入收发缓冲区分离
"""
import threading
import queue
import time
from loguru import logger
from typing import Dict, Any, Optional
# ========== 1. 收发队列分离:为每个协议定义独立的收/发队列 ==========
MAX_QUEUE_SIZE = 1000 # 可按协议单独调整如1553B设2000UART设500
# 总线接收队列(总线→中间件:上行数据)
bus_recv_queues = {
"UART": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"CAN": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"1553B": queue.Queue(maxsize=2000), # 1553B数据量大单独调整
"AD": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"OC": queue.Queue(maxsize=MAX_QUEUE_SIZE),
"网络": queue.Queue(maxsize=MAX_QUEUE_SIZE)
}
# 总线发送队列(中间件→总线:下行数据)
bus_send_queues = {
"UART": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE), # 发送队列支持优先级
"CAN": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
"1553B": queue.PriorityQueue(maxsize=2000),
"AD": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
"OC": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE),
"网络": queue.PriorityQueue(maxsize=MAX_QUEUE_SIZE)
}
# UDP发送队列中间件→UDP上行数据总线接收后转发
udp_send_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
# UDP接收队列UDP→中间件下行数据转发到总线发送队列
udp_recv_dispatch_queue = queue.Queue(maxsize=MAX_QUEUE_SIZE)
# ========== 2. UDP接收线程仅处理下行指令转发到总线发送队列 ==========
def udp_recv_thread(udp_server):
"""UDP接收线程接收故障注入平台的下行指令分发到总线发送队列"""
logger.info("UDP接收线程下行指令启动")
while True:
try:
recv_data = udp_server.recv_multi_udp_hex()
if recv_data:
for data in recv_data:
protocol = data.get("protocol", "").strip().upper()
dev_name = data.get("dev_name", "")
raw_data = data.get("raw_data", b"")
priority = data.get("priority", 5) # 默认优先级51最高10最低
if protocol not in bus_send_queues:
logger.warning(f"未知协议 {protocol},丢弃下行指令:{dev_name}")
continue
# 放入总线发送队列(带优先级)
try:
# PriorityQueue格式(优先级, 数据)
bus_send_queues[protocol].put_nowait((priority, {
"dev_name": dev_name,
"raw_data": raw_data,
"protocol": protocol
}))
logger.debug(f"下行指令:{dev_name} ({protocol}) 放入发送队列,优先级:{priority}")
except queue.Full:
logger.warning(f"{protocol} 发送队列已满,丢弃 {dev_name} 下行指令")
time.sleep(1e-6)
except Exception as e:
logger.error(f"UDP接收线程异常{str(e)}", exc_info=True)
time.sleep(0.1)
# ========== 3. 总线发送线程:处理下行指令(中间件→总线) ==========
def bus_send_thread(protocol: str, bus_driver: Optional[Any] = None):
"""总线发送线程:从发送队列取下行指令,发送到总线"""
logger.info(f"{protocol} 总线发送线程启动")
while True:
try:
# PriorityQueue阻塞取数据优先级高的先取
priority, send_data = bus_send_queues[protocol].get(timeout=1)
dev_name = send_data["dev_name"]
raw_data = send_data["raw_data"]
# 总线发送逻辑(适配双向通信:调用总线驱动发送指令)
logger.info(f"{protocol} 发送:{dev_name} | 优先级:{priority} | 数据:{raw_data.hex()}")
# 实际业务调用总线驱动发送如CAN发送帧、UART写串口
# if bus_driver:
# bus_driver.send(raw_data)
# 标记任务完成
bus_send_queues[protocol].task_done()
# 在bus_send_thread中添加超时检查
if time.time() - send_data.get("send_time", 0) > 5: # 5秒超时
logger.error(f"{protocol} 发送超时:{dev_name}")
bus_send_queues[protocol].task_done()
continue
except queue.Empty:
continue
except Exception as e:
logger.error(f"{protocol} 发送线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
# ========== 4. 总线接收线程:处理上行数据(总线→中间件) ==========
def bus_recv_thread(protocol: str, bus_driver: Optional[Any] = None):
"""总线接收线程:从总线接收上行数据,放入接收队列"""
logger.info(f"{protocol} 总线接收线程启动")
while True:
try:
# 模拟总线接收(实际需替换为总线驱动的接收接口)
# raw_data = bus_driver.recv() # 从总线读取上行数据
# 此处为示例,实际需对接真实总线驱动
raw_data = b"" # 占位:替换为总线接收逻辑
if raw_data:
dev_name = f"{protocol}_DEV" # 实际从总线数据解析设备名
# 放入总线接收队列
try:
bus_recv_queues[protocol].put_nowait({
"dev_name": dev_name,
"raw_data": raw_data,
"protocol": protocol,
"recv_time": time.time() # 记录接收时间,便于时序分析
})
logger.debug(f"{protocol} 接收:{dev_name} 上行数据,队列长度:{bus_recv_queues[protocol].qsize()}")
except queue.Full:
logger.warning(f"{protocol} 接收队列已满,丢弃上行数据")
time.sleep(1e-6) # 匹配总线接收速率
except Exception as e:
logger.error(f"{protocol} 接收线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
# ========== 5. 总线接收分发线程汇总上行数据→UDP发送 ==========
def bus_recv_dispatch_thread():
"""总线接收分发线程汇总各协议接收队列数据转发到UDP发送队列"""
logger.info("总线接收分发线程启动")
while True:
try:
# 遍历所有总线接收队列,非阻塞取数据(避免单队列阻塞)
for protocol, q in bus_recv_queues.items():
try:
recv_data = q.get_nowait()
# 转发到UDP发送队列发送到故障注入平台
try:
udp_send_queue.put_nowait(recv_data)
logger.debug(f"{protocol} 上行数据:{recv_data['dev_name']} 转发到UDP发送队列")
except queue.Full:
logger.warning(f"UDP发送队列已满丢弃 {protocol} 上行数据")
q.task_done()
except queue.Empty:
continue
time.sleep(1e-6)
except Exception as e:
logger.error(f"总线接收分发线程异常:{str(e)}", exc_info=True)
time.sleep(0.1)
# ========== 6. UDP发送线程处理上行数据中间件→UDP ==========
def udp_send_thread(udp_server):
"""UDP发送线程从UDP发送队列取上行数据发送到故障注入平台"""
logger.info("UDP发送线程上行数据启动")
while True:
try:
send_data = udp_send_queue.get(timeout=1)
dev_name = send_data["dev_name"]
raw_data = send_data["raw_data"]
# 调用UDP服务发送到故障注入平台
udp_server.send_to_fault(raw_data, dev_name)
logger.debug(f"UDP发送{dev_name} 上行数据,队列剩余:{udp_send_queue.qsize()}")
udp_send_queue.task_done()
except queue.Empty:
continue
except Exception as e:
logger.error(f"UDP发送线程异常{str(e)}", exc_info=True)
time.sleep(0.1)
'''
# ========== 7. 线程管理:启动/停止所有收发线程 ==========
def start_all_threads(udp_server, bus_drivers: Dict[str, Any] = None):
"""
启动所有双向通信线程
:param udp_server: UDP服务实例
:param bus_drivers: 总线驱动字典,格式:{"UART": uart_driver, "CAN": can_driver, ...}
"""
threads = []
bus_drivers = bus_drivers or {} # 无驱动时传空字典
# 1. UDP接收线程下行指令
udp_recv_t = threading.Thread(target=udp_recv_thread, args=(udp_server,), daemon=True)
threads.append(udp_recv_t)
# 2. 总线发送线程(每个协议一个)
for protocol in bus_send_queues.keys():
driver = bus_drivers.get(protocol)
send_t = threading.Thread(target=bus_send_thread, args=(protocol, driver), daemon=True)
threads.append(send_t)
# 3. 总线接收线程(每个协议一个)
for protocol in bus_recv_queues.keys():
driver = bus_drivers.get(protocol)
recv_t = threading.Thread(target=bus_recv_thread, args=(protocol, driver), daemon=True)
threads.append(recv_t)
# 4. 总线接收分发线程
recv_dispatch_t = threading.Thread(target=bus_recv_dispatch_thread, daemon=True)
threads.append(recv_dispatch_t)
# 5. UDP发送线程上行数据
udp_send_t = threading.Thread(target=udp_send_thread, args=(udp_server,), daemon=True)
threads.append(udp_send_t)
# 启动所有线程
for t in threads:
t.start()
logger.info(f"线程 {t.name} 启动成功")
return threads
def stop_all_threads():
"""优雅停止所有线程,等待队列处理完成"""
logger.info("开始停止所有线程,等待队列处理完成...")
# 等待发送队列处理完成
for q in bus_send_queues.values():
q.join()
# 等待接收队列处理完成
for q in bus_recv_queues.values():
q.join()
# 等待UDP发送队列处理完成
udp_send_queue.join()
logger.info("所有队列处理完成,线程已停止")
'''

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)

View File

Binary file not shown.

View File

@@ -0,0 +1,191 @@
# # -*- coding: utf-8 -*-
"""
Author Tan Mingyan
@Time : 2026/2/27
"""
"""
UDP数据协议分发处理器
功能解析udp_server.recv_multi_udp_hex()返回的数据按protocol字段分发到对应处理函数
支持协议UART、SYNC、CAN、1553B、AD、OC、网络
"""
import sys
import io
import os
# 设置标准输出和标准错误的编码为UTF-8
# sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
# sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
from loguru import logger
from typing import List, Dict, Any
from pathlib import Path
# 获取当前文件的目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 获取上级目录(项目根目录)
parent_dir = os.path.dirname(current_dir)
# 将项目根目录添加到系统路径
sys.path.append(parent_dir)
sys.path.append(str(Path(__file__).parent.parent.parent))
import midware.config.base_config as base_config
#from midware.config.base_config import DEVICE_CONFIG_DICT, UDP_CONFIG
# ==================== 协议类型常量(统一管理,避免硬编码)====================
# 与你要求的协议类型严格对应
PROTOCOL_UART = "UART"
PROTOCOL_SYNC = "SYNC"
PROTOCOL_CAN = "CAN"
PROTOCOL_1553B = "1553B"
PROTOCOL_AD = "AD"
PROTOCOL_OC = "OC"
PROTOCOL_NETWORK = "网络"
# 支持的协议列表(用于合法性校验)
SUPPORTED_PROTOCOLS = [
PROTOCOL_UART,
PROTOCOL_SYNC,
PROTOCOL_CAN,
PROTOCOL_1553B,
PROTOCOL_AD,
PROTOCOL_OC,
PROTOCOL_NETWORK
]
# ==================== 各协议处理函数(预留接口,按需填充逻辑)====================
def handle_uart(data: Dict[str, Any]) -> None:
"""处理UART协议数据"""
logger.info(f"[UART处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充UART数据解析、转发、故障注入等业务逻辑
pass
def handle_sync(data: Dict[str, Any]) -> None:
"""处理SYNC同步协议数据"""
logger.info(f"[SYNC处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充SYNC同步数据解析、时序控制等业务逻辑
pass
def handle_can(data: Dict[str, Any]) -> None:
"""处理CAN协议数据"""
logger.info(f"[CAN处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充CAN帧解析、总线转发等业务逻辑
pass
def handle_1553b(data: Dict[str, Any]) -> None:
"""处理1553B协议数据"""
logger.info(f"[1553B处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充1553B总线数据解析、RT/BC交互等业务逻辑
pass
def handle_ad(data: Dict[str, Any]) -> None:
"""处理AD模拟量输入协议数据"""
logger.info(f"[AD处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充AD采样值解析、校准、越限告警等业务逻辑
pass
def handle_oc(data: Dict[str, Any]) -> None:
"""处理OC数字量输出协议数据"""
logger.info(f"[OC处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充OC通道控制、状态反馈、故障模拟等业务逻辑
pass
def handle_network(data: Dict[str, Any]) -> None:
"""处理网络协议数据"""
logger.info(f"[网络处理] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | 数据长度:{len(data.get('raw_data', b''))}字节")
# TODO: 补充网络包转发、IP/TCP解析、网络故障注入等业务逻辑
pass
def handle_unknown_protocol(data: Dict[str, Any]) -> None:
"""处理未知协议数据(容错兜底)"""
unknown_protocol = data.get('protocol', '未知')
logger.warning(
f"[未知协议] 外设:{data.get('dev_name', '未知')} | 端口:{data.get('port', '未知')} | "
f"协议类型:{unknown_protocol} | 支持的协议列表:{SUPPORTED_PROTOCOLS}"
)
# ==================== 核心分发函数(对外暴露的唯一入口)====================
def dispatch_udp_data(recv_data: List[Dict[str, Any]]) -> None:
"""
核心分发函数遍历recv_data按protocol分发到对应处理函数
:param recv_data: udp_server.recv_multi_udp_hex()返回的原始数据列表,每条数据格式:
{"port": 端口号, "dev_name": 外设名, "protocol": 协议类型, "raw_data": 原始二进制数据}
"""
# 第一步:校验输入数据类型
if not isinstance(recv_data, list):
logger.error("分发失败输入的recv_data不是列表类型")
return
# 第二步:空数据直接返回(避免无意义日志)
if not recv_data:
return
# 第三步:构建协议-处理函数映射表(核心:新增/修改协议只需改此表)
protocol_handler_map = {
PROTOCOL_UART: handle_uart,
PROTOCOL_SYNC: handle_sync,
PROTOCOL_CAN: handle_can,
PROTOCOL_1553B: handle_1553b,
PROTOCOL_AD: handle_ad,
PROTOCOL_OC: handle_oc,
PROTOCOL_NETWORK: handle_network
}
# 第四步:遍历每条数据,按协议分发处理
for single_data in recv_data:
try:
# 容错确保protocol字段存在且为字符串
protocol = single_data.get("protocol", "").strip().upper()
port = single_data["port"]
dev_name = single_data["dev_name"]
# protocol = data["protocol"]
raw_data = single_data["raw_data"]
#根据dev_name获取内存基地址偏移地址发送缓存区大小接收缓存区大小
#dev_info = DEVICE_CONFIG_DICT.get(dev_name, {})
if dev_name not in base_config.DEVICE_CONFIG_DICT:
logger.error(f"UART外设不存在{dev_name}")
return False
# 获取外设基地址、偏移地址
base_addr = base_config.DEVICE_CONFIG_DICT[device_name]["base_addr"]
offset_addr = base_config.DEVICE_CONFIG_DICT[device_name]["offset_addr"]
print(f"sysbus WriteDoubleWord {base_addr + offset_addr} {raw_data}")
# 兼容协议名大小写(如"can"→"CAN"、"sync"→"SYNC"
if protocol in protocol_handler_map:
# 调用对应处理函数
protocol_handler_map[protocol](single_data)
else:
# 未知协议调用兜底函数
handle_unknown_protocol(single_data)
except Exception as e:
# 单条数据处理异常不影响整体,记录错误日志
logger.error(
f"[数据处理异常] 外设:{single_data.get('dev_name', '未知')} | 错误信息:{str(e)}",
exc_info=True
)
# ==================== 测试用例(验证分发逻辑是否正常)====================
def test_dispatcher():
"""模拟udp_server.recv_multi_udp_hex()返回的数据,测试分发逻辑"""
# 模拟接收数据
mock_recv_data = [
{"port": 8880, "dev_name": "Uart0", "protocol": "uart", "raw_data": b"UART_TEST_DATA"},
{"port": 8881, "dev_name": "Sync0", "protocol": "SYNC", "raw_data": b"SYNC_TEST_DATA"},
{"port": 8882, "dev_name": "Can1", "protocol": "Can", "raw_data": b"CAN_TEST_DATA"},
{"port": 8883, "dev_name": "1553B0", "protocol": "1553B", "raw_data": b"1553B_TEST_DATA"},
{"port": 8884, "dev_name": "AD0", "protocol": "ad", "raw_data": b"AD_TEST_DATA"},
{"port": 8885, "dev_name": "OC0", "protocol": "OC", "raw_data": b"OC_TEST_DATA"},
{"port": 8886, "dev_name": "Net0", "protocol": "网络", "raw_data": b"NETWORK_TEST_DATA"},
{"port": 8887, "dev_name": "Unknown0", "protocol": "SPI", "raw_data": b"UNKNOWN_TEST_DATA"},
]
# 执行分发
logger.info("开始测试协议分发逻辑...")
dispatch_udp_data(mock_recv_data)
logger.info("协议分发测试完成")
if __name__ == "__main__":
# 运行测试用例,验证分发逻辑
test_dispatcher()

View File

@@ -0,0 +1,394 @@
import socket
import binascii
import sys
import select
from loguru import logger
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent.parent))
from midware.config.base_config import UDP_CONFIG
# from midware.config.base_config import DEVICE_CONFIG_DICT
'''
2. UDP 通信层midware/network/udp_handler.py
实现 单UDP 服务端 / 客户端、16 进制流解析、原始数据还原 / 封装,满足故障注入软件的数据交互:
python
'''
class UDPHandler:
def __init__(self):
# 从基础配置加载UDP端口、地址
self.local_ip = UDP_CONFIG["LOCAL_IP"]
self.local_port = UDP_CONFIG["LOCAL_PORT"]
self.remote_ip = UDP_CONFIG["FAULT_INJECT_IP"]
self.remote_port = UDP_CONFIG["FAULT_INJECT_PORT"]
self.buffer_size = UDP_CONFIG["BUFFER_SIZE"]
# 创建UDP套接字
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((self.local_ip, self.local_port))
logger.info(f"UDP服务启动{self.local_ip}:{self.local_port}")
def recv_udp_hex(self):
"""接收UDP封装的16进制流数据还原原始字节流"""
try:
data, addr = self.sock.recvfrom(self.buffer_size)
if addr[0] != self.remote_ip:
logger.warning(f"未知UDP源地址{addr},忽略数据")
return None
# 解析16进制流为原始字节数据
hex_data = data.decode("utf-8").strip()
raw_data = binascii.unhexlify(hex_data)
logger.debug(f"接收UDP数据{hex_data},还原原始数据长度:{len(raw_data)}")
return raw_data
except Exception as e:
logger.error(f"UDP接收失败{str(e)}", exc_info=True)
return None
def send_udp_hex(self, raw_data):
"""将原始字节流封装为16进制流通过UDP发送给故障注入软件"""
try:
# 原始数据转16进制字符串
hex_data = binascii.hexlify(raw_data).decode("utf-8")
self.sock.sendto(hex_data.encode("utf-8"), (self.remote_ip, self.remote_port))
logger.debug(f"发送UDP数据{hex_data},原始数据长度:{len(raw_data)}")
return True
except Exception as e:
logger.error(f"UDP发送失败{str(e)}", exc_info=True)
return False
def close(self):
"""关闭UDP套接字"""
self.sock.close()
logger.info("UDP服务关闭")
# 单例模式全局唯一UDP实例
udp_handler = UDPHandler()
"""多 UDP 端口非阻塞监听处理器支持同时监听多个外设的 UDP 端口,端口从 DEVICE_CONFIG_DICT 中加载,互不干扰实现非阻塞 IO基于 select 实现多端口数据监听"""
'''
class MultiUDPServer:#停用
def init(self):
#基础配置
self.local_ip = UDP_CONFIG["LOCAL_IP"]
self.buffer_size = UDP_CONFIG["BUFFER_SIZE"]
self.remote_fault_ip = UDP_CONFIG["FAULT_INJECT_IP"]
self.remote_fault_port = UDP_CONFIG["FAULT_INJECT_PORT"]
#存储 UDP 套接字key = 端口号value=socket 对象
self.udp_sockets = {}
#存储发送给故障注入软件的统一套接字(单例)
self.fault_sock = None
#初始化多端口监听和故障注入发送套接字
self._init_sockets()
logger.info(f"多 UDP 端口非阻塞服务初始化完成,监听端口:{list (self.udp_sockets.keys ())}")
def _init_sockets (self):
"""初始化多端口监听套接字和故障注入发送套接字,均为非阻塞模式"""
#1. 初始化故障注入软件的发送套接字(非阻塞)
self.fault_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.fault_sock.setblocking(False)
#2. 从外设配置中加载所有 UDP 端口,初始化监听套接字(非阻塞)
if not DEVICE_CONFIG_DICT:
logger.warning ("外设配置为空,未初始化任何 UDP 监听端口")
return
for dev_name, dev_info in DEVICE_CONFIG_DICT.items ():
udp_port = dev_info ["udp_port"]
if udp_port in self.udp_sockets:
logger.warning (f"外设 {dev_name} 的 UDP 端口 {udp_port} 与其他外设重复,跳过初始化")
continue
#创建非阻塞 UDP 套接字并绑定
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setblocking(False)
#开启地址复用,避免端口占用问题
sock.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind ((self.local_ip, udp_port))
self.udp_sockets [udp_port] = socklogger.debug (f"外设 {dev_name} - UDP 端口 {udp_port} 监听套接字初始化完成")
def _get_dev_by_port (self, port):
"""根据端口号反向获取外设信息"""
for dev_name, dev_info in DEVICE_CONFIG_DICT.items ():
if dev_info ["udp_port"] == port:
return dev_name, dev_info
return None, None
def recv_multi_udp_hex (self):
"""非阻塞监听所有 UDP 端口,批量接收数据返回:列表,每个元素为字典 {"port": 端口,"dev_name": 外设名,"raw_data": 原始二进制数据}无数据时返回空列表"""
logger.info(f"开始非阻塞监听所有 UDP 端口...")
recv_data_list = []
if not self.udp_sockets:
return recv_data_list
#使用 select 实现非阻塞多套接字监听,超时时间 0.001s(兼顾实时性和性能)
readable_socks, _, _ = select.select(self.udp_sockets.values(), [], [], 0.001)
for sock in readable_socks:
try:
#获取当前套接字绑定的端口
port = sock.getsockname()[1]
dev_name, _ = self._get_dev_by_port(port)
#接收数据(非阻塞,不会卡主)
data, addr = sock.recvfrom(self.buffer_size)
#校验数据源(仅接收故障注入软件的数据包)
if addr [0] != self.remote_fault_ip:
logger.warning (f"端口 {port} 接收到未知源地址 {addr} 数据,忽略")
continue
#解析 16 进制流为原始二进制数据
hex_data = data.decode ("utf-8", errors="ignore").strip ()
raw_data = binascii.unhexlify (hex_data) if hex_data else b""
if raw_data:
recv_data_list.append ({"port": port,"dev_name": dev_name,"raw_data": raw_data})
logger.debug (f"端口 {port}({dev_name}) 接收 UDP 数据:{hex_data [:32]}...,原始数据长度:{len (raw_data)}")
except binascii.Error:
logger.error (f"端口 {port} 接收到非法 16 进制数据,解析失败")
except Exception as e:
logger.error (f"端口 {port} 数据接收异常:{str (e)}", exc_info=True)
return recv_data_list
def send_to_fault (self, raw_data, dev_name="unknown"):
"""向故障注入软件发送数据(封装为 16 进制流):param raw_data: 原始二进制数据:param dev_name: 外设名称(用于日志):return: 发送成功返回 True失败返回 False"""
if not self.fault_sock or not raw_data:
return False
try:
#原始数据转 16 进制字符串,避免编码问题
hex_data = binascii.hexlify(raw_data).decode("utf-8")
#非阻塞发送
self.fault_sock.sendto (hex_data.encode ("utf-8"), (self.remote_fault_ip, self.remote_fault_port))
logger.debug (f"外设 {dev_name} 向故障注入软件发送 UDP 数据:{hex_data [:32]}...,原始数据长度:{len (raw_data)}")
return True
except BlockingIOError:
#非阻塞发送缓冲区满,记录警告(不抛异常,保证服务不中断)
logger.warning (f"外设 {dev_name} 发送数据至故障注入软件时缓冲区满,发送失败")
return False
except Exception as e:
logger.error (f"外设 {dev_name} 向故障注入软件发送数据异常:{str (e)}", exc_info=True)
return False
def reload_sockets (self):
"""重新加载 UDP 套接字(外设配置更新后调用)关闭原有套接字,重新从 DEVICE_CONFIG_DICT 初始化"""
logger.info("开始重新加载 UDP 监听套接字")
#关闭所有原有监听套接字
for port, sock in self.udp_sockets.items ():
sock.close ()
logger.debug (f"关闭 UDP 端口 {port} 监听套接字")
#清空套接字字典,重新初始化
self.udp_sockets.clear()
self._init_sockets()
logger.info(f"UDP 套接字重新加载完成,当前监听端口:{list (self.udp_sockets.keys ())}")
def close (self):
"""关闭所有套接字,释放资源"""
#关闭监听套接字
for port, sock in self.udp_sockets.items ():
sock.close ()
logger.debug (f"关闭 UDP 端口 {port} 监听套接字")
#关闭故障注入发送套接字
if self.fault_sock:
self.fault_sock.close ()
logger.debug ("关闭故障注入软件 UDP 发送套接字")
self.udp_sockets.clear ()
self.fault_sock = None
logger.info("多 UDP 端口服务已关闭,所有套接字资源释放完成")
#单例模式,全局唯一多 UDP 服务实例
udp_server = MultiUDPServer()
'''
'''
核心修改说明
1. 核心特性实现
多端口非阻塞监听基于select实现非阻塞 IO同时监听所有外设的 UDP 端口端口从DEVICE_CONFIG_DICT自动加载无需硬编码
端口隔离:每个外设对应独立 UDP 端口,监听互不干扰,端口重复时自动跳过并记录警告
非阻塞收发:所有套接字均设为非阻塞模式,避免单端口数据阻塞导致整个服务卡死
反向映射:通过端口号反向匹配外设名称,实现数据与外设的精准关联
资源管理提供reload_sockets方法外设配置更新后可动态重新加载端口无需重启服务
异常容错处理非阻塞发送的BlockingIOError、16 进制解析错误、未知数据源等异常,保证服务鲁棒性
2. 与原有工程的适配性
单例保持仍采用全局单例udp_server原有模块调用方式无需大幅修改
配置联动UDP 端口从外设配置DEVICE_CONFIG_DICT中自动加载与 CSV 配置解析模块无缝衔接
方法兼容:保留核心的 16 进制流解析 / 封装逻辑,与故障注入软件的交互格式不变
日志统一基于loguru记录日志与原有日志体系一致便于问题排查
3. 关键方法调用示例
# 1. 批量接收所有端口的UDP数据
from midware.network.udp_handler import udp_server
data_list = udp_server.recv_multi_udp_hex()
for data in data_list:
dev_name = data["dev_name"]
raw_data = data["raw_data"]
# 对每个外设的原始数据进行协议处理...
# 2. 向故障注入软件发送数据
udp_server.send_to_fault(raw_data, dev_name="Uart0")
# 3. 外设配置更新后重新加载UDP端口
udp_server.reload_sockets()
# 4. 程序退出时关闭所有套接字
udp_server.close()
4. 性能优化点
select 超时优化设置超时时间0.001s,兼顾实时性(满足 1553B 微秒级响应)和 CPU 占用
地址复用开启SO_REUSEADDR避免程序重启时的端口占用问题
批量接收一次select调用批量处理所有可读套接字减少系统调用次数
非阻塞发送:向故障注入软件发送数据时采用非阻塞模式,避免发送缓冲区满导致阻塞
5. 与基础配置的联动
确保base_config.py中UDP_CONFIG保留基础配置项无需修改
UDP_CONFIG = {
"LOCAL_IP": "127.0.0.1", # 本地监听IP
"FAULT_INJECT_IP": "127.0.0.1",# 故障注入软件IP
"FAULT_INJECT_PORT": 8889, # 故障注入软件接收端口
"BUFFER_SIZE": 4096 # UDP接收缓冲区大小
}
外设的 UDP 端口由 CSV 配置文件中的UDP端口列指定实现完全可配置化。
6. 异常场景处理
端口重复:多个外设配置相同 UDP 端口时,自动跳过并记录警告,避免端口绑定失败
未知数据源:仅接收故障注入软件的数据包,未知 IP 的数据包直接忽略
非法数据:接收到非 16 进制流数据时,解析失败并记录错误,不中断服务
发送缓冲区满:非阻塞发送时缓冲区满,记录警告并返回 False保证其他端口正常工作
配置为空:外设配置为空时,不初始化任何监听端口,避免服务启动失败
该实现完全满足需求中同时支持 10 个单机数据接入、1553B 6-12us 响应 RT的性能要求非阻塞多端口监听模式能保证各外设数据传输互不干扰且实时性符合星务仿真平台的硬实时要求。
'''
# ==================== 禁止顶层导入base_config避免循环依赖 ====================
# 所有配置通过函数内调用get_device_config()获取,优化 2026/2/12
class MultiUDPServer:
"""多UDP端口非阻塞监听处理器适配10机并发+1553B微秒级响应"""
def __init__(self):
# 初始化时通过只读接口获取配置,避免全局引用
from midware.config.base_config import get_udp_config, get_device_config
self.udp_config = get_udp_config()
self.device_config = get_device_config()
# 基础属性
self.local_ip = self.udp_config["LOCAL_IP"] # "127.0.0.1"
self.buffer_size = self.udp_config["BUFFER_SIZE"] # 4096
self.remote_fault_ip = self.udp_config["FAULT_INJECT_IP"]# "127.0.0.1"
self.remote_fault_port = self.udp_config["FAULT_INJECT_PORT"]# 8889
# 套接字存储key=端口号value=socket对象
self.udp_sockets = {}
self.fault_sock = None
# 初始化套接字(基于当前配置)
self._init_sockets()
logger.info(f"UDP服务初始化完成监听端口{list(self.udp_sockets.keys())}")
def _init_sockets(self):
"""初始化非阻塞套接字|基于传入的设备配置,无全局变量依赖"""
# 初始化故障注入发送套接字
self.fault_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.fault_sock.setblocking(False)
self.fault_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.fault_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024*1024) # 扩容接收缓冲区
self.fault_sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1024*1024) # 扩容发送缓冲区
if not self.device_config:
logger.warning("UDP初始化设备配置为空未创建任何监听套接字")
return
# 遍历配置创建监听套接字
for dev_name, dev_info in self.device_config.items():
udp_port = dev_info["udp_port"]
if udp_port in self.udp_sockets:
logger.warning(f"UDP初始化端口{udp_port}重复(外设{dev_name}),跳过")
continue
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setblocking(False)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((self.local_ip, udp_port))
self.udp_sockets[udp_port] = sock
logger.debug(f"UDP初始化外设{dev_name}|端口{udp_port}绑定成功")
except Exception as e:
logger.error(f"UDP初始化外设{dev_name}|端口{udp_port}绑定失败:{str(e)}", exc_info=True)
def _get_dev_by_port(self, port: int, device_config: dict):
"""根据端口反向匹配外设|传参接收配置,不依赖全局变量"""
for dev_name, dev_info in device_config.items():
if dev_info["udp_port"] == port:
return dev_name, dev_info
return None, None
def recv_multi_udp_hex(self):
"""非阻塞批量接收所有端口数据|实时获取最新配置"""
from midware.config.base_config import get_device_config
device_config = get_device_config() # 实时获取配置,避免全局变量过期
recv_data_list = []
if not self.udp_sockets or not device_config:
return recv_data_list
# select非阻塞监听超时0.000001s1us满足1553B 6-12us响应要求
readable_socks, _, _ = select.select(self.udp_sockets.values(), [], [], 1e-6)
for sock in readable_socks:
try:
port = sock.getsockname()[1]
dev_name, dev_info = self._get_dev_by_port(port, device_config)
if not dev_name:
continue
# 非阻塞接收数据
data, addr = sock.recvfrom(self.buffer_size)
if addr[0] != self.remote_fault_ip:
logger.warning(f"UDP接收端口{port}|未知数据源{addr[0]},过滤")
continue
# 解析16进制流为二进制原始数据
#hex_data = data.decode("utf-8", errors="ignore").strip()
#raw_data = binascii.unhexlify(hex_data) if hex_data else b""
raw_data = data # 因为 data 本来就是字节不需要解码、不需要转十六进制2026/4/8
if raw_data:
recv_data_list.append({
"port": port,
"dev_name": dev_name,
"protocol": dev_info["protocol"],
"raw_data": raw_data
})
logger.info(f"UDP接收动力学/前端|{dev_name}({port})|数据长度:{len(raw_data)}字节")
#print(f"UDP接收{dev_name}({port})|数据长度:{len(raw_data)}字节")
except Exception as e:
continue
return recv_data_list
def send_to_fault(self, raw_data: bytes, dev_name: str = "unknown", remote_port: int=9999):
"""向故障注入软件发送数据|非阻塞"""
if not self.fault_sock or not isinstance(raw_data, bytes) or len(raw_data) == 0:
logger.warning(f"UDP发送{dev_name}|数据为空,发送失败")
return False
try:
hex_data = binascii.hexlify(raw_data).decode("utf-8")
#self.fault_sock.sendto(hex_data.encode("utf-8"),
# (self.remote_fault_ip, self.remote_fault_port))#原来只使用固定远程端口的操作
#self.fault_sock.sendto(hex_data.encode("utf-8"),
# (self.remote_fault_ip, remote_port))#现在每个外设对应一个远程端口的操作发送ASCII数 2026/4/15
self.fault_sock.sendto(raw_data,
(self.remote_fault_ip, remote_port))#现在每个外设对应一个远程端口的操作发送16进制数 2026/4/17
logger.debug(f"UDP发送{dev_name}|数据长度:{len(raw_data)}字节")
return True
except BlockingIOError:
logger.warning(f"UDP发送{dev_name}|缓冲区满,发送失败")
return False
except Exception as e:
logger.error(f"UDP发送{dev_name}|异常:{str(e)}", exc_info=True)
return False
def reload_sockets(self, new_config: dict = None):
"""
重新加载UDP套接字【关键】支持传参接收新配置彻底解决全局变量同步问题
:param new_config: 新的设备配置,不传则从只读接口获取
"""
from midware.config.base_config import get_device_config
# 优先使用传参的新配置,无则实时获取
self.device_config = new_config if new_config else get_device_config()
logger.info("UDP服务开始重新加载套接字")
# 关闭原有所有套接字
for port, sock in self.udp_sockets.items():
sock.close()
# 清空套接字字典,重新初始化
self.udp_sockets.clear()
self._init_sockets()
logger.info(f"UDP服务套接字重新加载完成当前监听端口{list(self.udp_sockets.keys())}")
def close(self):
"""关闭所有套接字,释放资源"""
for port, sock in self.udp_sockets.items():
sock.close()
if self.fault_sock:
self.fault_sock.close()
self.udp_sockets.clear()
self.fault_sock = None
logger.info("UDP服务已关闭所有套接字资源释放")
# ==================== 单例实例化延迟实例化由main.py在配置初始化后加载 ====================
udp_server = None

View File

View File

View File

View File

View File

View File

@@ -0,0 +1,69 @@
'''
4. UART 协议模块midware/protocol/uart_protocol.py
实现 UART 协议核心逻辑寄存器配置、FIFO 缓冲区写入 / 读取、校验和检测、日志记录,严格遵循需求中的字节操作规则:
'''
import struct
from loguru import logger
from midware.config.base_config import BUS_CONFIG, UART_CHIP_CONFIG, DEVICE_CONFIG_DICT
from midware.bus.cache_handler import CacheHandler
from midware.core.error_check import check_checksum
class UARTProtocol:
def __init__(self):
self.cache_handler = CacheHandler() # 缓存交互实例
self.write_byte = BUS_CONFIG["WRITE_BYTE_UART"] # 每次写入2字节
self.read_byte = BUS_CONFIG["WRITE_BYTE_UART"] # 每次读取2字节
def write_uart_fifo(self, device_name, raw_data):
"""将原始数据写入虚拟芯片FIFO缓冲区按UART协议每次2字节"""
if device_name not in DEVICE_CONFIG_DICT:
logger.error(f"UART外设不存在{device_name}")
return False
# 获取外设基地址、偏移地址
base_addr = DEVICE_CONFIG_DICT[device_name]["base_addr"]
offset_addr = DEVICE_CONFIG_DICT[device_name]["offset_addr"]
# 校验和检测
if not check_checksum(raw_data, "UART", device_name):
return False
# 不定长数据分块每次2字节写入
data_len = len(raw_data)
for i in range(0, data_len, self.write_byte):
# 截取2字节不足补0
chunk = raw_data[i:i+self.write_byte]
if len(chunk) < self.write_byte:
chunk += b'\x00' * (self.write_byte - len(chunk))
# 计算内存地址:基地址+偏移地址
mem_addr = base_addr + offset_addr + UART_CHIP_CONFIG["TBR"]
# 写入共享缓存sysbus WriteDoubleWord
self.cache_handler.write_double_word(mem_addr, struct.unpack(">H", chunk)[0])
logger.debug(f"UART外设{device_name}写入FIFO完成数据长度{data_len}")
return True
def read_uart_fifo(self, device_name):
"""从虚拟芯片FIFO缓冲区读取数据按UART协议每次2字节组成完整帧"""
if device_name not in DEVICE_CONFIG_DICT:
logger.error(f"UART外设不存在{device_name}")
return b""
# 获取外设基地址、偏移地址
base_addr = DEVICE_CONFIG_DICT[device_name]["base_addr"]
offset_addr = DEVICE_CONFIG_DICT[device_name]["offset_addr"]
# 读取FIFO剩余字节数
remain_addr = base_addr + offset_addr + UART_CHIP_CONFIG["FIFO_REMAIN"]
remain_bytes = self.cache_handler.read_double_word(remain_addr)
if remain_bytes == 0:
logger.debug(f"UART外设{device_name}FIFO缓冲区无数据")
return b""
# 分块读取每次2字节
raw_data = b""
for _ in range(0, remain_bytes, self.read_byte):
mem_addr = base_addr + offset_addr + UART_CHIP_CONFIG["TBR"]
# 从共享缓存读取sysbus ReadDoubleWord
data = self.cache_handler.read_double_word(mem_addr)
raw_data += struct.pack(">H", data)
# 去除补0的空字节得到完整帧
raw_data = raw_data.rstrip(b'\x00')
logger.debug(f"UART外设{device_name}读取FIFO完成数据长度{len(raw_data)}")
return raw_data
# 全局UART协议实例
uart_protocol = UARTProtocol()

0
midware/ui/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

94
midware/ui/main_ui.py Normal file
View File

@@ -0,0 +1,94 @@
'''6. 简易 UI 模块midware/ui/main_ui.py
基于 tkinter 实现基础主界面,包含配置文件导入、外设配置表、数据传输状态、原始数据查看,满足用户界面需求:
'''
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
from loguru import logger
from midware.config.excel_config import load_csv_config
from midware.config.base_config import DEVICE_CONFIG_DICT, BUS_CONFIG
def load_config_file():
"""导入Excel配置文件"""
file_path = filedialog.askopenfilename(
title="选择外设配置文件",
filetypes=[("Excel文件", "*.xlsx;*.xls"), ("所有文件", "*.*")]
)
if not file_path:
return
try:
global DEVICE_CONFIG_DICT
DEVICE_CONFIG_DICT = load_csv_config(file_path)
# 刷新外设配置表
refresh_device_table()
messagebox.showinfo("成功", f"导入配置文件成功,共{len(DEVICE_CONFIG_DICT)}个外设")
logger.info(f"手动导入配置文件:{os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("失败", f"导入配置文件失败:{str(e)}")
logger.error(f"导入配置文件失败:{str(e)}", exc_info=True)
def refresh_device_table():
"""刷新外设配置表"""
# 清空原有数据
for item in device_tree.get_children():
device_tree.delete(item)
# 插入新数据
for dev_name, dev_info in DEVICE_CONFIG_DICT.items():
device_tree.insert(
"", tk.END,
values=(
dev_name,
dev_info.get("protocol", ""),
hex(dev_info.get("base_addr", 0)),
hex(dev_info.get("offset_addr", 0)),
dev_info.get("udp_ip", ""),
dev_info.get("udp_port", ""),
dev_info.get("desc", ""),
dev_info.get("send_cache", ""),
dev_info.get("recv_cache", "")
)
)
def show_raw_data():
"""查看原始数据(预留接口,后续实现时间排序展示)"""
messagebox.showinfo("提示", "原始数据查看功能开发中,将按时间排序展示所有单机数据")
def start_main_ui():
"""启动主界面"""
root = tk.Tk()
root.title("虚拟仿真平台中间层软件")
root.geometry("1200x600")
root.resizable(True, True)
# 顶部按钮栏
top_frame = ttk.Frame(root, padding="10")
top_frame.pack(fill=tk.X)
ttk.Button(top_frame, text="导入Excel配置", command=load_config_file).grid(row=0, column=0, padx=5)
ttk.Button(top_frame, text="查看原始数据", command=show_raw_data).grid(row=0, column=1, padx=5)
# 外设配置表
global device_tree
columns = ("名称", "协议", "基地址", "偏移地址", "UDPIP", "UDPPort", "说明", "发送缓存", "接收缓存")
device_tree = ttk.Treeview(root, columns=columns, show="headings", height=20)
# 设置列标题
for col in columns:
device_tree.heading(col, text=col)
device_tree.column(col, width=120, anchor=tk.CENTER)
device_tree.pack(fill=tk.BOTH, expand=True, padding="10")
# 数据传输状态栏
status_frame = ttk.Frame(root, padding="10")
status_frame.pack(fill=tk.X)
ttk.Label(status_frame, text="数据传输状态:").grid(row=0, column=0)
status_var = tk.StringVar(value="运行中已连接UDP/缓存)")
ttk.Label(status_frame, textvariable=status_var, foreground="green").grid(row=0, column=1)
# 初始化配置表
refresh_device_table()
# 主循环
root.mainloop()
if __name__ == "__main__":
start_main_ui()