2026-06-03 17:33:24 +08:00
|
|
|
|
"""MIL SDK 核心数据模型"""
|
2026-07-10 13:35:29 +08:00
|
|
|
|
import os
|
|
|
|
|
|
import json
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
from dataclasses import dataclass, field, fields
|
2026-06-03 17:33:24 +08:00
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
2026-07-10 13:35:29 +08:00
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class Config:
|
|
|
|
|
|
"""MIL SDK 全局配置
|
|
|
|
|
|
|
|
|
|
|
|
承载测试工程相关的全局开关与路径信息,是 UI 层与 core 层之间的
|
|
|
|
|
|
配置传输载体。序列化使用 JSON 文件持久化。
|
|
|
|
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
|
|
DataPath: 仿真数据目录(Excel 原始文件所在路径)
|
|
|
|
|
|
FilePath: 当前打开的 Excel 文件路径
|
|
|
|
|
|
AddTimeEn: 用例步骤时间是否按累加方式记录
|
|
|
|
|
|
GeratePath: 是否为生成的用例另存新文件
|
|
|
|
|
|
CurrProject: 当前工程名称
|
|
|
|
|
|
ItemConfigs: 各模块/用例项的细粒度配置
|
|
|
|
|
|
"""
|
|
|
|
|
|
DataPath:str = ""
|
|
|
|
|
|
FilePath:str = ""
|
|
|
|
|
|
AddTimeEn:bool = True
|
|
|
|
|
|
GeratePath:bool = True
|
|
|
|
|
|
CurrProject:str = ""
|
|
|
|
|
|
# dict 是可变类型,必须用 default_factory 显式实例化,避免多个 Config 共享同一对象
|
|
|
|
|
|
ItemConfigs: dict = field(default_factory=dict)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
|
|
"""将 Config 实例序列化为 dict,便于写入 JSON 文件。"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
"DataPath":self.DataPath,
|
|
|
|
|
|
"FilePath":self.FilePath,
|
|
|
|
|
|
"AddTimeEn":self.AddTimeEn,
|
|
|
|
|
|
"GeratePath":self.GeratePath,
|
|
|
|
|
|
"CurrProject":self.CurrProject,
|
|
|
|
|
|
"ItemConfigs":self.ItemConfigs
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def load_config(self, config_path: str) -> "Config":
|
|
|
|
|
|
"""从 JSON 文件读取配置并填充到当前实例的各个字段。
|
|
|
|
|
|
|
|
|
|
|
|
解析规则:
|
|
|
|
|
|
- JSON 中存在的字段会被回写到 Config 的对应字段;
|
|
|
|
|
|
- JSON 中缺失的字段保持当前 Config 实例的默认值;
|
|
|
|
|
|
- JSON 中多余字段被忽略(向前兼容:增加字段不会破坏老配置)。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
config_path: 配置文件路径。
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
self:填充后的 Config 实例,便于链式调用。
|
|
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
|
FileNotFoundError: 配置文件不存在。
|
|
|
|
|
|
json.JSONDecodeError: 文件内容不是合法 JSON。
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 以 UTF-8 读取 JSON,避免 Windows 默认编码带来的乱码问题
|
|
|
|
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
raw = json.load(f)
|
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
|
# 显式记录并重抛,遵循"错误必须显式处理,禁止静默失败"
|
|
|
|
|
|
logger.error(f"配置文件{config_path}未找到")
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
if not isinstance(raw, dict):
|
|
|
|
|
|
# 文件不合法(非 dict 根节点)→ 用一个空 dict 填充,保持实例可用
|
|
|
|
|
|
raw = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 用反射取出本类已声明的字段名白名单,避免 JSON 脏字段污染
|
|
|
|
|
|
allowed = {f.name for f in fields(self.__class__)}
|
|
|
|
|
|
for key, value in raw.items():
|
|
|
|
|
|
if key in allowed:
|
|
|
|
|
|
setattr(self, key, value)
|
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def save_config(self,config_path:str):
|
|
|
|
|
|
"""将当前配置以 JSON 格式写入磁盘。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
config_path: 配置文件路径。
|
|
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
|
Exception: 写入失败时记录日志并原样抛出异常。
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(config_path,'w',encoding='utf-8') as f:
|
|
|
|
|
|
json.dump(self.to_dict(), f, indent=4, ensure_ascii=False)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"配置文件{config_path}写入失败{e.args}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-03 17:33:24 +08:00
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class DataLog:
|
|
|
|
|
|
"""仿真数据日志记录
|
|
|
|
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
|
|
time: 时间戳(秒)
|
|
|
|
|
|
value: 信号值(可以是任意类型)
|
|
|
|
|
|
"""
|
|
|
|
|
|
time: float = 0.0
|
|
|
|
|
|
value: str = ""
|
|
|
|
|
|
|
2026-06-04 13:53:31 +08:00
|
|
|
|
|
2026-06-03 17:33:24 +08:00
|
|
|
|
@dataclass
|
|
|
|
|
|
class SignalData:
|
|
|
|
|
|
"""信号数据封装
|
|
|
|
|
|
|
2026-07-10 13:35:29 +08:00
|
|
|
|
描述 Excel 中某一列信号的完整信息:
|
|
|
|
|
|
- 所在列号(column)
|
|
|
|
|
|
- 列上方的属性行(attributes,例如 Signal Name / BlockPath 等)
|
|
|
|
|
|
- 时间-值采样序列(datalog)
|
|
|
|
|
|
|
2026-06-03 17:33:24 +08:00
|
|
|
|
Attributes:
|
2026-07-10 13:35:29 +08:00
|
|
|
|
column: 信号在 Excel 工作表中的列索引(1-based)
|
|
|
|
|
|
attributes: 列上方各属性行(行号 -> 文本)的字典映射
|
|
|
|
|
|
datalog: 时间戳-数值采样点列表
|
2026-06-03 17:33:24 +08:00
|
|
|
|
"""
|
2026-06-04 15:28:53 +08:00
|
|
|
|
# signal_type: str | None = None
|
2026-06-03 17:33:24 +08:00
|
|
|
|
column: int = 0
|
2026-06-04 15:28:53 +08:00
|
|
|
|
attributes: dict[str, str] = field(default_factory=dict)
|
2026-06-03 17:33:24 +08:00
|
|
|
|
datalog: list[DataLog] = field(default_factory=list)
|
|
|
|
|
|
|
2026-06-04 15:28:53 +08:00
|
|
|
|
|
2026-06-03 17:33:24 +08:00
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
2026-07-10 13:35:29 +08:00
|
|
|
|
"""将 SignalData 转为 dict,便于跨层传输与持久化。
|
2026-06-03 17:33:24 +08:00
|
|
|
|
|
|
|
|
|
|
Returns:
|
2026-07-10 13:35:29 +08:00
|
|
|
|
包含 column、attributes、datalog 字段的字典。
|
2026-06-03 17:33:24 +08:00
|
|
|
|
"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
"column": self.column,
|
2026-06-04 15:28:53 +08:00
|
|
|
|
"attributes": self.attributes,
|
2026-06-03 17:33:24 +08:00
|
|
|
|
"datalog": self.datalog
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class ExcelDataResult:
|
|
|
|
|
|
"""Excel 数据读取结果封装
|
|
|
|
|
|
|
|
|
|
|
|
提供对 Excel 数据的类型安全访问,隐藏内部实现细节
|
|
|
|
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
|
|
sheet_name: 工作表名称
|
|
|
|
|
|
source_row: Source: Input 所在行号
|
|
|
|
|
|
signals: 信号名称到信号数据的映射
|
|
|
|
|
|
"""
|
|
|
|
|
|
sheet_name: str = "Scenario1"
|
|
|
|
|
|
source_row: int = 0
|
|
|
|
|
|
signals: dict[str, SignalData] = field(default_factory=dict)
|
|
|
|
|
|
|
|
|
|
|
|
def get_signal(self, name: str) -> SignalData | None:
|
|
|
|
|
|
"""获取指定信号的数据
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
name: 信号名称
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
信号数据对象,如果不存在返回 None
|
|
|
|
|
|
"""
|
|
|
|
|
|
return self.signals.get(name)
|
|
|
|
|
|
|
|
|
|
|
|
def get_signal_names(self) -> list[str]:
|
|
|
|
|
|
"""获取所有信号名称
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
信号名称列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
return list(self.signals.keys())
|
|
|
|
|
|
|
|
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
|
|
|
|
"""转换为字典格式(兼容旧 API)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
包含所有信号的字典,保留原有的数据结构
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = {
|
|
|
|
|
|
"sheet_name": self.sheet_name,
|
|
|
|
|
|
"source_row": self.source_row
|
|
|
|
|
|
}
|
|
|
|
|
|
for name, signal in self.signals.items():
|
|
|
|
|
|
result[name] = signal.to_dict()
|
|
|
|
|
|
return result
|