Files
virtual_simulation_midware/midware/config/excel_config-20260226-02.py

135 lines
7.7 KiB
Python
Raw Normal View History

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