Files
virtual_simulation_midware/midware/config/excel_config-20260226.py
2026-06-16 15:40:19 +08:00

137 lines
9.3 KiB
Python
Raw Blame History

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