first commit
This commit is contained in:
0
midware/bus/__init__.py
Normal file
0
midware/bus/__init__.py
Normal file
55
midware/bus/cache_handler.py
Normal file
55
midware/bus/cache_handler.py
Normal 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()
|
||||
0
midware/bus/chip_register.py
Normal file
0
midware/bus/chip_register.py
Normal file
0
midware/config/__init__.py
Normal file
0
midware/config/__init__.py
Normal file
BIN
midware/config/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/config/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/config/__pycache__/base_config.cpython-311.pyc
Normal file
BIN
midware/config/__pycache__/base_config.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/config/__pycache__/excel_config.cpython-311.pyc
Normal file
BIN
midware/config/__pycache__/excel_config.cpython-311.pyc
Normal file
Binary file not shown.
179
midware/config/base_config.py
Normal file
179
midware/config/base_config.py
Normal 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()
|
||||
11
midware/config/device_config.csv
Normal file
11
midware/config/device_config.csv
Normal 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
|
||||
|
135
midware/config/excel_config-20260226-02.py
Normal file
135
midware/config/excel_config-20260226-02.py
Normal 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):
|
||||
"""处理十六进制字符串转整数(兼容带逗号 / 空格的格式,如 0x2080,0000、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)
|
||||
137
midware/config/excel_config-20260226.py
Normal file
137
midware/config/excel_config-20260226.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Excel 配置解析(excel_config.py):基于openpyxl实现 Excel 文件解析,提取外设名称、UDP 端口、协议类型、基地址等信息,存入DEVICE_CONFIG_DICT。
|
||||
'''
|
||||
'''
|
||||
### 核心功能说明
|
||||
1. **格式兼容**:支持解析Excel中**带逗号/空格的十六进制地址**(如`0x2080,0000`→`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`、`0x2080,0000`、`0x0000`);
|
||||
5. 端口/缓存大小填写**纯数字**。
|
||||
|
||||
### 简单测试
|
||||
在`config_files/`目录下创建`device_config.xlsx`,填入如下测试数据,直接运行该文件即可看到解析结果:
|
||||
|
||||
| 名称 | 协议类型 | 内存基地址 | 内存偏移地址 | UDP端口 | 说明 | 发送缓存区大小 | 接收缓存区大小 |
|
||||
|--------|----------|------------|--------------|---------|------------|----------------|----------------|
|
||||
| Uart0 | UART | 0x2080,0000 | 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):
|
||||
"""处理十六进制字符串转整数(兼容带逗号 / 空格的格式,如 0x2080,0000、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)
|
||||
155
midware/config/excel_config.py
Normal file
155
midware/config/excel_config.py
Normal 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):
|
||||
"""处理十六进制字符串转整数(兼容带逗号 / 空格的格式,如 0x2080,0000、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
23
midware/core/__init__.py
Normal 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"
|
||||
BIN
midware/core/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/core/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/core/__pycache__/bus_udp_decoupler.cpython-311.pyc
Normal file
BIN
midware/core/__pycache__/bus_udp_decoupler.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
midware/core/__pycache__/data_forward.cpython-311.pyc
Normal file
BIN
midware/core/__pycache__/data_forward.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/core/__pycache__/error_check.cpython-311.pyc
Normal file
BIN
midware/core/__pycache__/error_check.cpython-311.pyc
Normal file
Binary file not shown.
0
midware/core/bus_drivers/1553b_driver.py
Normal file
0
midware/core/bus_drivers/1553b_driver.py
Normal file
0
midware/core/bus_drivers/__init__.py
Normal file
0
midware/core/bus_drivers/__init__.py
Normal file
0
midware/core/bus_drivers/ad_driver.py
Normal file
0
midware/core/bus_drivers/ad_driver.py
Normal file
78
midware/core/bus_drivers/base_driver.py
Normal file
78
midware/core/bus_drivers/base_driver.py
Normal 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__} 自动断开连接")
|
||||
0
midware/core/bus_drivers/can_driver.py
Normal file
0
midware/core/bus_drivers/can_driver.py
Normal file
0
midware/core/bus_drivers/oc_driver.py
Normal file
0
midware/core/bus_drivers/oc_driver.py
Normal file
0
midware/core/bus_drivers/uart_driver.py
Normal file
0
midware/core/bus_drivers/uart_driver.py
Normal file
637
midware/core/bus_udp_decoupler.py
Normal file
637
midware/core/bus_udp_decoupler.py
Normal 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},无法调整优先级")
|
||||
0
midware/core/data_forward.py
Normal file
0
midware/core/data_forward.py
Normal file
97
midware/core/error_check.py
Normal file
97
midware/core/error_check.py
Normal 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
|
||||
Binary file not shown.
203
midware/core/history/bus_udp_decoupler_20260306.py
Normal file
203
midware/core/history/bus_udp_decoupler_20260306.py
Normal 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("所有队列处理完成,线程已停止")
|
||||
'''
|
||||
234
midware/core/history/bus_udp_decoupler_20260307.py
Normal file
234
midware/core/history/bus_udp_decoupler_20260307.py
Normal 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设2000,UART设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) # 默认优先级5(1最高,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("所有队列处理完成,线程已停止")
|
||||
|
||||
'''
|
||||
37
midware/drivers/__init__.py
Normal file
37
midware/drivers/__init__.py
Normal 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
|
||||
BIN
midware/drivers/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/base_driver.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/base_driver.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_1553b.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_1553b.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_ad.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_ad.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_can.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_can.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_network.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_network.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_oc.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_oc.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/bus_uart.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/bus_uart.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/renode_agent.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/renode_agent.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/drivers/__pycache__/tcp_uart_agent.cpython-311.pyc
Normal file
BIN
midware/drivers/__pycache__/tcp_uart_agent.cpython-311.pyc
Normal file
Binary file not shown.
78
midware/drivers/base_driver.py
Normal file
78
midware/drivers/base_driver.py
Normal 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__} 自动断开连接")
|
||||
100
midware/drivers/bus_1553b.py
Normal file
100
midware/drivers/bus_1553b.py
Normal 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
190
midware/drivers/bus_ad.py
Normal 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发送数据|帧ID:0x{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
133
midware/drivers/bus_can.py
Normal 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发送数据|帧ID:0x{frame_id:X}|扩展帧:{is_extended}|"
|
||||
f"数据:{data.hex()}|长度:{len(data)}字节"
|
||||
)
|
||||
# logger.info(
|
||||
# #f"UART发送数据|帧ID:0x{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
|
||||
73
midware/drivers/bus_network.py
Normal file
73
midware/drivers/bus_network.py
Normal 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发送数据|帧ID:0x{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
73
midware/drivers/bus_oc.py
Normal 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发送数据|帧ID:0x{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
131
midware/drivers/bus_uart.py
Normal 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发送数据|帧ID:0x{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
|
||||
528
midware/drivers/renode_agent.py
Normal file
528
midware/drivers/renode_agent.py
Normal 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
|
||||
)
|
||||
226
midware/drivers/tcp_uart_agent.py
Normal file
226
midware/drivers/tcp_uart_agent.py
Normal 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)
|
||||
0
midware/network/__init__.py
Normal file
0
midware/network/__init__.py
Normal file
BIN
midware/network/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/network/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/network/__pycache__/protocol_dispatcher.cpython-311.pyc
Normal file
BIN
midware/network/__pycache__/protocol_dispatcher.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/network/__pycache__/udp_handler.cpython-311.pyc
Normal file
BIN
midware/network/__pycache__/udp_handler.cpython-311.pyc
Normal file
Binary file not shown.
191
midware/network/protocol_dispatcher.py
Normal file
191
midware/network/protocol_dispatcher.py
Normal 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()
|
||||
394
midware/network/udp_handler.py
Normal file
394
midware/network/udp_handler.py
Normal 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.000001s(1us),满足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
|
||||
0
midware/protocol/__init__.py
Normal file
0
midware/protocol/__init__.py
Normal file
0
midware/protocol/ad_protocol.py
Normal file
0
midware/protocol/ad_protocol.py
Normal file
0
midware/protocol/b1553b_protocol.py
Normal file
0
midware/protocol/b1553b_protocol.py
Normal file
0
midware/protocol/can_protocol.py
Normal file
0
midware/protocol/can_protocol.py
Normal file
0
midware/protocol/oc_protocol.py
Normal file
0
midware/protocol/oc_protocol.py
Normal file
69
midware/protocol/uart_protocol.py
Normal file
69
midware/protocol/uart_protocol.py
Normal 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
0
midware/ui/__init__.py
Normal file
BIN
midware/ui/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
midware/ui/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
midware/ui/__pycache__/main_ui.cpython-311.pyc
Normal file
BIN
midware/ui/__pycache__/main_ui.cpython-311.pyc
Normal file
Binary file not shown.
94
midware/ui/main_ui.py
Normal file
94
midware/ui/main_ui.py
Normal 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()
|
||||
Reference in New Issue
Block a user