first commit

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

View File

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