Files
2026-06-04 15:39:36 +08:00

872 lines
23 KiB
Markdown
Raw Permalink 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.
# MIL SDK 文档
MIL SDK 是一个用于处理 MILModel-In-the-Loop)仿真数据的 Python 工具包,主要提供 Excel 数据文件的读取、解析、生成和更新功能。
## 目录
- [概述](#概述)
- [项目架构](#项目架构)
- [快速开始](#快速开始)
- [数据模型](#数据模型)
- [异常体系](#异常体系)
- [日志模块](#日志模块)
- [Excel 读取模块](#excel-读取模块)
- [Excel 生成模块](#excel-生成模块)
- [Excel 更新模块](#excel-更新模块)
- [API 参考](#api-参考)
- [使用示例](#使用示例)
- [Excel 文件格式约定](#excel-文件格式约定)
---
## 概述
### 项目简介
MIL SDK 专为 MIL 仿真数据处理场景设计,提供以下核心能力:
- **数据读取** - 从特定格式的 Excel 文件中解析仿真信号数据
- **用例管理** - 读取和解析测试用例模板
- **用例生成** - 根据数据和模板生成测试用例 Excel 文件
- **数据对比** - 比较新旧仿真数据差异,自动更新信号列
### 技术栈
| 类别 | 技术选型 |
|------|----------|
| 语言 | Python 3.10+ |
| 主要依赖 | openpyxl >= 3.0.0 |
| 测试框架 | pytest >= 7.0.0 |
| 类型检查 | mypy >= 1.0.0 |
| 代码规范 | ruff >= 0.1.0 |
### 版本信息
- 当前版本:0.1.0
- 许可证:MIT
---
## 项目架构
### 目录结构
```
mil_sdk/
├── src/
│ └── core/ # 核心模块
│ ├── __init__.py # 公共 API 导出
│ ├── base.py # 核心数据模型
│ ├── exceptions.py # 自定义异常
│ ├── logging_config.py # 日志配置
│ ├── mil_read_data_excel.py # 读取仿真数据 Excel
│ ├── mil_read_case_excel.py # 读取测试用例 Excel
│ ├── mil_create_data_excel.py # 生成测试用例 Excel
│ └── mil_update_excel.py # 更新用例 Excel
├── tests/ # 测试模块
│ ├── conftest.py # pytest 配置和 fixtures
│ ├── test_base.py # 基础数据模型测试
│ ├── test_mil_case_excel.py # 用例 Excel 测试
│ └── test_mil_data_excel.py # 数据 Excel 测试
├── main.py # 入口文件
├── pyproject.toml # 项目配置
├── requirements.txt # 依赖清单
└── SDK_DOCUMENTATION.md # 本文档
```
### 模块职责
| 模块 | 文件 | 职责 |
|------|------|------|
| 数据模型 | base.py | 定义 DataLog、SignalData、ExcelDataResult 等核心数据结构 |
| 异常体系 | exceptions.py | 定义 MILSDKError 及其子类,统一异常处理 |
| 日志模块 | logging_config.py | 提供日志配置和获取功能 |
| 数据读取 | mil_read_data_excel.py | 解析 MIL 仿真数据 Excel,提取信号数据 |
| 用例读取 | mil_read_case_excel.py | 解析测试用例模板 Excel |
| 用例生成 | mil_create_data_excel.py | 根据模板生成测试用例 Excel |
| 用例更新 | mil_update_excel.py | 比较新旧数据,更新信号列 |
### 依赖关系
```
┌─────────────────────────────────────────────────────────────────┐
│ main.py │
│ (入口文件,演示 SDK 使用) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ src/core/__init__.py │
│ (公共 API 统一导出模块) │
└─────────────────────────────────────────────────────────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌────────┐ ┌──────┐ ┌────────┐ ┌──────┐ ┌──────┐
│ base │ │excep │ │logging│ │ mil_ │ │ mil_ │
│ │ │tions │ │_conf │ │read_ │ │create│
│ │ │ │ │ │ │data │ │_data │
└────────┘ └──────┘ └───────┘ └──────┘ └──────┘
│ │
▼ ▼
┌────────┐ ┌────────┐
│ mil_ │ │ mil_ │
│read_ │ │update_ │
│case │ │excel │
└────────┘ └────────┘
```
---
## 快速开始
### 安装
```bash
pip install -e .
```
### 开发依赖安装
```bash
pip install -e ".[dev]"
```
### 基础使用
```python
from src.core import setup_logging, read_excel_data, ExcelReaderConfig
# 初始化日志
setup_logging()
# 读取仿真数据
config = ExcelReaderConfig()
result = read_excel_data("simulation.xlsx", return_object=True, config=config)
# 获取信号数据
signal = result.get_signal("signal_name")
if signal:
for log in signal.datalog:
print(f"时间: {log.time}, 值: {log.value}")
```
---
## 数据模型
### DataLog
仿真数据日志记录,表示单个时间点的数据。
```python
@dataclass
class DataLog:
time: float = 0.0 # 时间戳(秒)
value: str = "" # 信号值
```
**属性说明**
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| time | float | 0.0 | 时间戳,单位为秒 |
| value | str | "" | 信号值,可以是任意类型 |
**示例**
```python
log = DataLog(time=1.5, value="on")
print(f"时间: {log.time}, 值: {log.value}")
```
---
### SignalData
信号数据封装,包含信号的元数据和数据日志。
```python
@dataclass
class SignalData:
column: int = 0 # 列索引
attributes: dict[str, str] = field(default_factory=dict) # 信号属性
datalog: list[DataLog] = field(default_factory=list) # 数据日志列表
```
**属性说明**
| 属性 | 类型 | 说明 |
|------|------|------|
| column | int | Excel 列索引 |
| attributes | dict[str, str] | 信号属性字典,键为行号,值为单元格值 |
| datalog | list[DataLog] | 数据日志列表 |
**方法说明**
| 方法 | 返回值 | 说明 |
|------|--------|------|
| to_dict() | dict[str, Any] | 转换为字典格式 |
**示例**
```python
signal = SignalData(column=2, attributes={"type": "boolean"}, datalog=[DataLog(0.0, "off")])
data = signal.to_dict()
```
---
### ExcelDataResult
Excel 数据读取结果封装,提供对 Excel 数据的类型安全访问。
```python
@dataclass
class ExcelDataResult:
sheet_name: str = "Scenario1" # 工作表名称
source_row: int = 0 # Source: Input 所在行号
signals: dict[str, SignalData] = field(default_factory=dict) # 信号字典
```
**属性说明**
| 属性 | 类型 | 说明 |
|------|------|------|
| sheet_name | str | 工作表名称,默认 "Scenario1" |
| source_row | int | "Source: Input" 标记所在行号 |
| signals | dict[str, SignalData] | 信号名称到信号数据的映射 |
**方法说明**
| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| get_signal() | name: str | SignalData \| None | 获取指定信号的数据 |
| get_signal_names() | - | list[str] | 获取所有信号名称 |
| to_dict() | - | dict[str, Any] | 转换为字典格式 |
**示例**
```python
result = ExcelDataResult(sheet_name="Scenario1", source_row=3, signals={})
signal = result.get_signal("signal_name")
names = result.get_signal_names()
data = result.to_dict()
```
---
### ExcelReaderConfig
Excel 读取配置类,用于配置 Excel 文件的解析规则。
```python
@dataclass
class ExcelReaderConfig:
sheet_name: str = "Scenario1" # 工作表名称
source_header: str = "Source: Input" # 数据源标记行文本
time_column: int = 1 # 时间列索引(A 列)
header_row: int = 1 # 信号名称所在行号
type_row: int = 3 # 信号类型所在行号
interp_row: int = 6 # 插值类型所在行号
data_start_row_offset: int = 1 # 数据起始行偏移量
output_header: str = "Source: Output" # 输出标记行文本
block_path_row: int = 4 # 块路径所在行号
```
**属性说明**
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| sheet_name | str | "Scenario1" | 工作表名称 |
| source_header | str | "Source: Input" | 数据源标记行文本 |
| time_column | int | 1 | 时间列索引(A 列) |
| header_row | int | 1 | 信号名称所在行号 |
| type_row | int | 3 | 信号类型所在行号 |
| interp_row | int | 6 | 插值类型所在行号 |
| data_start_row_offset | int | 1 | 相对于 source_row 的数据起始行偏移量 |
| output_header | str | "Source: Output" | 输出标记行文本 |
| block_path_row | int | 4 | 块路径所在行号 |
**示例**
```python
config = ExcelReaderConfig(
sheet_name="Scenario1",
source_header="Source: Input",
time_column=1
)
```
---
## 异常体系
异常模块位于 [exceptions.py](file:///f:/MyProject/mil_sdk/src/core/exceptions.py)。
### 异常层次
```
Exception
└── MILSDKError (基础异常)
├── ExcelReadError (文件读取错误)
├── ExcelFormatError (格式错误)
├── CaseDataError (用例数据错误)
└── ExcelWriteError (文件写入错误)
```
### MILSDKError
基础异常类,所有 SDK 自定义异常的基类。
```python
class MILSDKError(Exception):
pass
```
### ExcelReadError
Excel 文件读取错误,当文件不存在或读取失败时抛出。
```python
class ExcelReadError(MILSDKError):
"""Excel 文件读取错误(文件不存在、权限问题等)"""
pass
```
**触发场景**
- 文件不存在
- 文件权限问题
- 文件格式损坏
### ExcelFormatError
Excel 格式错误,当 Excel 格式不符合预期时抛出。
```python
class ExcelFormatError(MILSDKError):
"""Excel 格式错误(缺少 Sheet、格式不匹配等)"""
pass
```
**触发场景**
- 缺少指定的 Sheet
- 缺少 "Source: Input" 标记
- 缺少模板版本号
### CaseDataError
用例数据错误,当用例数据异常时抛出。
```python
class CaseDataError(MILSDKError):
"""用例数据错误(信号不存在、类型错误等)"""
pass
```
**触发场景**
- 信号名称不存在
- 时间格式错误
- 缺少用例标题
### ExcelWriteError
Excel 文件写入错误,当文件写入失败时抛出。
```python
class ExcelWriteError(MILSDKError):
"""Excel 文件写入错误(权限问题、保存失败等)"""
pass
```
**触发场景**
- 文件权限问题
- 磁盘空间不足
- 保存失败
**异常处理示例**
```python
from src.core import (
MILSDKError,
ExcelReadError,
ExcelFormatError,
CaseDataError,
ExcelWriteError,
read_excel_data,
)
try:
result = read_excel_data("data.xlsx")
except ExcelReadError as e:
print(f"文件读取失败: {e}")
except ExcelFormatError as e:
print(f"格式错误: {e}")
except MILSDKError as e:
print(f"SDK 错误: {e}")
```
---
## 日志模块
日志模块位于 [logging_config.py](file:///f:/MyProject/mil_sdk/src/core/logging_config.py)。
### setup_logging
配置日志系统,设置日志级别、输出目标等。
```python
def setup_logging(
level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO",
log_file: str | Path | None = "logs/mil_sdk.log",
console_output: bool = True,
) -> logging.Logger
```
**参数说明**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| level | str | "INFO" | 日志级别,可选 "DEBUG"、"INFO"、"WARNING"、"ERROR" |
| log_file | str \| Path \| None | "logs/mil_sdk.log" | 日志文件路径,设为 None 则不写入文件 |
| console_output | bool | True | 是否输出到控制台 |
**返回值**:根日志记录器 (logging.Logger)
**日志格式**`%(asctime)s [%(levelname)s] %(message)s`
**示例**
```python
from src.core import setup_logging
logger = setup_logging(level="DEBUG", log_file="logs/mil_sdk.log")
```
### get_logger
获取指定名称的日志记录器。
```python
def get_logger(name: str) -> logging.Logger
```
**参数说明**
| 参数 | 类型 | 说明 |
|------|------|------|
| name | str | 日志记录器名称,通常使用 `__name__` |
**返回值**:日志记录器实例
**示例**
```python
from src.core import get_logger
logger = get_logger(__name__)
logger.info("This is an info message")
```
---
## Excel 读取模块
### read_excel_data
读取 MIL 仿真 Excel 文件并解析信号数据。
```python
def read_excel_data(
excel_path: str,
return_object: bool = False,
config: ExcelReaderConfig | None = None
) -> DataDict | ExcelDataResult
```
**参数说明**
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| excel_path | str | - | Excel 文件路径 |
| return_object | bool | False | 是否返回 ExcelDataResult 对象(推荐),False 保持向后兼容 |
| config | ExcelReaderConfig \| None | None | Excel 读取配置,默认使用 ExcelReaderConfig() |
**返回值**
`return_object=False` 时返回包含以下键的字典(向后兼容):
- `wb`: Workbook 对象
- `sheet`: Worksheet 对象
- `source_row`: Source: Input 所在行号
- `<信号名>`: 信号元数据(type, column, datalog
`return_object=True` 时返回 ExcelDataResult 对象(推荐):
- `sheet_name`: 工作表名称
- `source_row`: Source: Input 所在行号
- `signals`: 信号名称到信号数据的映射
**异常**
| 异常类型 | 触发条件 |
|----------|----------|
| ExcelReadError | 文件不存在或读取失败 |
| ExcelFormatError | Excel 格式不符合预期 |
**示例**
```python
from src.core import read_excel_data
# 使用推荐的方式
result = read_excel_data("simulation.xlsx", return_object=True)
for name in result.get_signal_names():
signal = result.get_signal(name)
if signal:
print(f"信号: {name}, 数据点: {len(signal.datalog)}")
# 使用向后兼容的方式
data = read_excel_data("simulation.xlsx")
wb = data["wb"]
sheet = data["sheet"]
```
### read_excel_case
读取 Excel 用例模板。
```python
def read_excel_case(
excel_path: str,
data_dict: dict,
addTimeEn: bool,
) -> CaseDict
```
**参数说明**
| 参数 | 类型 | 说明 |
|------|------|------|
| excel_path | str | Excel 模板文件路径 |
| data_dict | dict | 信号数据字典 |
| addTimeEn | bool | 是否累加时间 |
**返回值**:用例字典
**异常**
| 异常类型 | 触发条件 |
|----------|----------|
| ExcelReadError | 文件读取失败 |
| ExcelFormatError | 格式错误 |
| CaseDataError | 用例数据错误 |
**用例字典结构**
```python
{
"Sheet名称": {
"用例标题": {
"enable": bool, # 是否启用
"step": {
"step0": {
"name": str, # 步骤名称
"time": float, # 时间
"action": dict # 操作字典
},
"step1": {...}
}
}
}
}
```
**示例**
```python
from src.core import read_excel_case, read_excel_data
data_dict = read_excel_data("simulation.xlsx", return_object=False)
cases = read_excel_case("template.xlsx", data_dict, addTimeEn=True)
for sheet_name, sheet_cases in cases.items():
print(f"Sheet: {sheet_name}")
for case_name, case_data in sheet_cases.items():
print(f" 用例: {case_name}, 启用: {case_data['enable']}")
```
---
## Excel 生成模块
### create_excel_case
根据数据和用例模板生成测试用例 Excel 文件。
```python
def create_excel_case(
excel_path: str,
data_dict: dict,
sheets_dict: dict,
) -> None
```
**参数说明**
| 参数 | 类型 | 说明 |
|------|------|------|
| excel_path | str | 输出目录路径 |
| data_dict | dict | 数据字典 |
| sheets_dict | dict | 工作表字典 |
**异常**
| 异常类型 | 触发条件 |
|----------|----------|
| CaseDataError | 数据异常 |
| ExcelWriteError | 文件写入错误 |
**示例**
```python
from src.core import read_excel_data, read_excel_case, create_excel_case
data_dict = read_excel_data("simulation.xlsx", return_object=False)
sheets_dict = read_excel_case("template.xlsx", data_dict, addTimeEn=True)
create_excel_case("output/", data_dict, sheets_dict)
```
---
## Excel 更新模块
### update_case_excel
比较新旧数据差异,更新 Excel 文件中的信号列。
```python
def update_case_excel(
filename: str,
old_data: dict,
new_data: dict,
) -> None
```
**参数说明**
| 参数 | 类型 | 说明 |
|------|------|------|
| filename | str | Excel 文件路径 |
| old_data | dict | 旧数据字典 |
| new_data | dict | 新数据字典 |
**功能说明**
- 比较新旧数据中的信号差异
- 在旧文件中添加新数据独有的信号列
- 从旧文件中删除新数据不存在的信号列
**异常**
| 异常类型 | 触发条件 |
|----------|----------|
| CaseDataError | 数据类型错误 |
**示例**
```python
from src.core import read_excel_data, update_case_excel
new_data = read_excel_data("new_data.xlsx", return_object=False)
old_data = read_excel_data("old_data.xlsx", return_object=False)
update_case_excel("updated.xlsx", old_data, new_data)
```
---
## API 参考
### 公共导入
所有公共 API 均可通过以下方式导入:
```python
from src.core import (
# 数据模型
DataLog,
SignalData,
ExcelDataResult,
ExcelReaderConfig,
# 核心函数
read_excel_data,
read_excel_case,
create_excel_case,
update_case_excel,
# 异常
MILSDKError,
ExcelReadError,
ExcelFormatError,
CaseDataError,
ExcelWriteError,
# 日志
setup_logging,
get_logger,
)
```
### 函数签名汇总
| 函数 | 签名 |
|------|------|
| DataLog | `DataLog(time: float = 0.0, value: str = "")` |
| SignalData | `SignalData(column: int = 0, attributes: dict = {}, datalog: list = [])` |
| ExcelDataResult | `ExcelDataResult(sheet_name: str = "Scenario1", source_row: int = 0, signals: dict = {})` |
| ExcelReaderConfig | `ExcelReaderConfig(...)` |
| read_excel_data | `read_excel_data(excel_path: str, return_object: bool = False, config: ExcelReaderConfig \| None = None) -> DataDict \| ExcelDataResult` |
| read_excel_case | `read_excel_case(excel_path: str, data_dict: dict, addTimeEn: bool) -> CaseDict` |
| create_excel_case | `create_excel_case(excel_path: str, data_dict: dict, sheets_dict: dict) -> None` |
| update_case_excel | `update_case_excel(filename: str, old_data: dict, new_data: dict) -> None` |
| setup_logging | `setup_logging(level: str = "INFO", log_file: str \| Path \| None = "logs/mil_sdk.log", console_output: bool = True) -> logging.Logger` |
| get_logger | `get_logger(name: str) -> logging.Logger` |
---
## 使用示例
### 基础用法
```python
from src.core import setup_logging, read_excel_data
setup_logging()
result = read_excel_data("simulation.xlsx", return_object=True)
signal_names = result.get_signal_names()
print(f"共解析 {len(signal_names)} 个信号")
for name in signal_names:
signal = result.get_signal(name)
if signal:
print(f" {name}: {len(signal.datalog)} 个数据点")
```
### 完整工作流
```python
from pathlib import Path
from src.core import (
setup_logging,
read_excel_data,
read_excel_case,
create_excel_case,
update_case_excel,
)
def main() -> None:
setup_logging(level="INFO")
excel_path = Path(__file__).parent
data_dict = read_excel_data("new_data.xlsx", return_object=False)
old_data_dict = read_excel_data("old_data.xlsx", return_object=False)
update_case_excel("new_case.xlsx", old_data_dict, data_dict)
if __name__ == "__main__":
main()
```
### 错误处理
```python
from src.core import (
ExcelReadError,
ExcelFormatError,
CaseDataError,
MILSDKError,
read_excel_data,
)
try:
result = read_excel_data("simulation.xlsx", return_object=True)
except ExcelReadError as e:
print(f"文件读取失败: {e}")
raise
except ExcelFormatError as e:
print(f"格式错误: {e}")
raise
except MILSDKError as e:
print(f"SDK 错误: {e}")
raise
```
---
## Excel 文件格式约定
### 仿真数据文件格式
Excel 仿真数据文件遵循以下格式约定:
| 行号 | 内容 |
|------|------|
| 1 | 信号名称(time, signal1, signal2... |
| 2 | 预留行(通常为空) |
| 3 | "Source: Input" 标记行,标识数据列开始 |
| 4 | 信号类型信息 |
| 5 | 块路径信息 |
| 6 | 插值类型信息 |
| 7+ | 时间-值数据对 |
**示例结构**
| A | B | C | D |
|---|---|---|---|
| time | signal1 | signal2 | signal3 |
| | | | |
| Source: Input | Source: Input | Source: Input | Source: Input |
| type | boolean | float | boolean |
| BlockPath | /model/signal1 | /model/signal2 | /model/signal3 |
| Interp | const | linear | zero-order hold |
| | off | 0.0 | true |
| 1.0 | on | 1.5 | false |
| 2.0 | off | 2.0 | true |
**要求**
- Sheet 名称:`Scenario1`
- 时间列:必须存在且列名为 "time"
- 跳过列:列名为 "Parameter:"、"Value"、"BlockPath" 的列会被跳过
### 用例模板文件格式
用例模板 Excel 文件遵循以下格式约定:
| Sheet 名称 | 内容 |
|-----------|------|
| Atech-Hefei | 模板版本号 |
| 其他 Sheet | 测试用例数据 |
**用例数据格式**
| 列号 | 列名 | 说明 |
|------|------|------|
| 1 | TITLE | 用例标题 |
| 2 | STATUS | 状态(完成测试/未完成) |
| 3 | ACTION | 操作描述(signal1=value1; signal2=value2 |
| 4 | NAME | 步骤名称 |
| 5 | TIME | 时间 |
**操作字符串格式**
- 多条操作使用分号 `;` 分隔
- 操作格式:`信号名=值`
- 示例:`signal1=on; signal2=0.5`
---
## 版本历史
### v0.1.0 (当前版本)
- 初始版本发布
- 支持 Excel 数据文件读取和解析
- 支持测试用例模板读取和生成
- 支持新旧数据对比和更新