目录
[2.1.FMU 文件的核心属性](#2.1.FMU 文件的核心属性)
[2.2.FMU 标准目录 / 文件结构](#2.2.FMU 标准目录 / 文件结构)
[2.3.核心文件 / 目录详解](#2.3.核心文件 / 目录详解)
[2.4.不同 FMI 模式的 FMU 结构差异](#2.4.不同 FMI 模式的 FMU 结构差异)
[5.4.编译脚本 build.sh](#5.4.编译脚本 build.sh)
1.简介
FMI 是一种开放标准 ,定义了在不同建模和仿真工具之间交换模型 和进行联合仿真的接口。通过 FMI,一个工具创建的模型可以被另一个工具使用,极大地促进了跨平台模型共享。
FMU (Functional Mock-up Unit)** 是 FMI 标准的核心产物,它是一个包含模型二进制文件和 XML 描述的 ZIP 包,可以在支持 FMI 的任何工具中运行。
fmilib 是 FMI (Functional Mock-up Interface) 标准的一个独立开源 C 语言实现库 ,用于在应用程序中集成和使用功能模型单元 (FMU)。它支持 FMI 标准的全部三个主要版本 :FMI 1.0、FMI 2.0 和 FMI 3.0。
主要功能
- FMU 导入:加载并执行 FMU 模型,支持模型交换 (Model Exchange) 和协同仿真 (Co-Simulation) 两种模式
- 模型初始化与执行:提供完整的生命周期管理,从创建 FMU 实例到仿真结束
- 变量访问:支持读写模型变量,包括标量和数组类型
- 事件处理:处理模型中的事件和状态变化
- 状态管理:支持模型状态的保存和恢复
技术特点
- 纯 C 实现:提供简洁的 C API,易于集成到各种应用程序中
- 跨平台支持:可在 Windows、Linux 和 macOS 等系统上编译运行
- 轻量级设计:最小化依赖,适合嵌入式和高性能计算场景
- 线程安全:支持多线程环境下的模型并行仿真
- 完整文档:提供详细的 API 文档和示例代码
2..FMU文件包
.FMU文件是遵循 FMI(Functional Mock-up Interface)标准 的压缩包(本质是 ZIP 格式),是跨工具 / 跨平台交换仿真模型的核心载体。它包含模型的元信息、二进制执行文件(或源码)、资源文件等,结构严格遵循 FMI 规范(1.0/2.0/3.0 版本略有差异,但核心一致)。
2.1.FMU 文件的核心属性
- 格式本质 :后缀为
.fmu,但可直接用 ZIP 解压工具(如 WinRAR、7-Zip、unzip命令)解压,解压后可见标准化目录结构。 - 核心目的:封装模型的 "描述信息 + 可执行逻辑 + 依赖资源",让不同仿真工具(如 Simulink、Dymola、OpenModelica)能无差别加载运行。
- 两种核心模式:FMU 分为「模型交换(ME)」和「协同仿真(CS)」,结构上的差异主要体现在二进制接口和 XML 描述中。
2.2.FMU 标准目录 / 文件结构
以下是 FMI 2.0/3.0 最典型的结构(FMI 1.0 仅目录命名略有差异,如 binaries 为 bin):
cpp
your_model.fmu (解压后)
├── modelDescription.xml # 必须:核心描述文件(FMU的"身份证")
├── binaries/ # 可选:不同平台的二进制库(核心执行文件)
│ ├── win32/ # Windows 32位:xxx.dll
│ ├── win64/ # Windows 64位:xxx.dll
│ ├── linux32/ # Linux 32位:libxxx.so
│ ├── linux64/ # Linux 64位:libxxx.so
│ └── darwin64/ # macOS 64位:libxxx.dylib
├── sources/ # 可选:模型源码(C/CMake等,用于源码级集成)
│ ├── model.c
│ ├── model.h
│ └── CMakeLists.txt
├── resources/ # 可选:模型依赖的资源文件
│ ├── parameters.csv # 参数配置文件
│ ├── lookup_table.dat # 查表数据
│ └── doc/ # 文档(说明、许可证等)
├── license.txt # 可选:许可证文件
└── readme.txt # 可选:使用说明
2.3.核心文件 / 目录详解
1.必须文件:modelDescription.xml
这是 FMU 的核心文件,所有工具 /fmilib 都会先解析该文件,包含模型的全部元信息,关键内容示例:
cpp
<?xml version="1.0" encoding="UTF-8"?>
<fmiModelDescription
fmiVersion="2.0" <!-- FMI版本:1.0/2.0/3.0 -->
modelName="EngineCoolingSystem" <!-- 模型名称 -->
guid="12345678-1234-1234-1234-1234567890AB" <!-- 唯一标识 -->
modelIdentifier="EngineCoolingSystem" <!-- 二进制库名称(无后缀) -->
generationTool="Dymola 2024" <!-- 生成FMU的工具 -->
variableNamingConvention="structured">
<!-- 模型类型:模型交换(ME)/协同仿真(CS) -->
<CoSimulation modelIdentifier="EngineCoolingSystem" />
<!-- <ModelExchange ... /> (若支持模型交换) -->
<!-- 变量定义(核心:每个变量有唯一的valueReference) -->
<ScalarVariables>
<!-- 输入变量:环境温度 -->
<ScalarVariable name="environment_temperature" valueReference="1" variability="continuous" causality="input">
<Real start="25.0" unit="degC" />
</ScalarVariable>
<!-- 输出变量:冷却液温度 -->
<ScalarVariable name="coolant_temperature" valueReference="2" variability="continuous" causality="output">
<Real unit="degC" />
</ScalarVariable>
<!-- 输出变量:水泵转速 -->
<ScalarVariable name="water_pump_speed" valueReference="3" variability="continuous" causality="output">
<Real unit="rpm" />
</ScalarVariable>
</ScalarVariables>
<!-- 二进制文件依赖(指向binaries下的库) -->
<Implementation>
<CoSimulation_Tool>
<BinaryFiles>
<BinaryFile platform="win64">binaries/win64/EngineCoolingSystem.dll</BinaryFile>
<BinaryFile platform="linux64">binaries/linux64/libEngineCoolingSystem.so</BinaryFile>
</BinaryFiles>
</CoSimulation_Tool>
</Implementation>
</fmiModelDescription>
fmiVersion:决定兼容的 FMI 解析库(如 fmilib 需匹配版本);modelIdentifier:二进制库的文件名(如 Windows 下为modelIdentifier.dll);ScalarVariables:所有可读写的变量(名称、类型、因果性(输入 / 输出)、单位、初始值等);BinaryFiles:不同平台的二进制文件路径。
2.可选目录:binaries/
存放不同操作系统 / 架构的二进制执行库,是模型的 "运行逻辑" 核心:
- Windows :
win32/*.dll(32 位)、win64/*.dll(64 位); - Linux :
linux32/lib*.so(32 位)、linux64/lib*.so(64 位); - macOS :
darwin64/lib*.dylib(64 位,FMI 3.0 也可能用macos64); - 若 FMU 仅提供源码(无
binaries),则需用户自行编译。
3.可选目录:sources/
存放模型的源码(通常是 C 语言,符合 FMI 接口规范),用于:
- 无对应平台二进制时,编译适配;
- 自定义修改模型逻辑;
- 审计模型实现。典型文件:
model.c(核心逻辑)、model.h(接口声明)、CMakeLists.txt(编译脚本)。
4.可选目录:resources/
存放模型运行依赖的非代码资源:
- 配置文件(如参数表、JSON/YAML 配置);
- 查表数据(如 MAP 图、曲线数据);
- 文档(如模型说明、变量手册);
- 其他依赖(如图片、日志模板)。
2.4.不同 FMI 模式的 FMU 结构差异
| 特征 | 模型交换(ME)FMU | 协同仿真(CS)FMU |
|---|---|---|
| 核心接口 | 状态 / 导数计算(由仿真器控制步长) | 自主执行仿真步(do_step 接口) |
| XML 标记 | 含 <ModelExchange> 节点 |
含 <CoSimulation> 节点 |
| 二进制逻辑 | 侧重 getDerivatives/setState |
侧重 doStep/setInput |
| 典型用途 | 离线仿真、积分控制 | 实时仿真、多模型联合仿真 |
3.安装与集成
3.1.下载地址
- 官方 GitHub 仓库 :https://github.com/modelon-community/fmi-library
- 官方网站 :www.fmi-library.org
- FMI 标准官网 :www.fmi-standard.org
- 文档:提供完整的 API 文档和示例
- Arch Linux 专属包源 :https://aur.archlinux.org/packages/fmilib
3.2.包管理器安装(便捷,优先推荐)
1.Linux 部分发行版
- Arch Linux/AUR :
yay -S fmilib(或paru -S fmilib); - Fedora(COPR):需先添加仓库,再安装(小众,建议源码编译);
- Ubuntu/Debian:无官方包,需源码编译或用 vcpkg。
2.macOS(Homebrew)
需先添加第三方 tap(官方 brew 无 fmilib):
cpp
# 添加tap并安装
brew tap modelon-community/tap
brew install fmilib
3.Windows(vcpkg)
vcpkg 是微软官方包管理器,可一键安装 fmilib:
cpp
# 打开VS开发者终端,执行:
vcpkg install fmilib:x64-windows # 64位Windows
# 若需32位:vcpkg install fmilib:x86-windows
安装后,vcpkg 会自动处理依赖,头文件路径:vcpkg_install_dir\installed\x64-windows\include,库文件路径:vcpkg_install_dir\installed\x64-windows\lib。
3.3.源码编译安装(通用,适配所有平台)
若包管理器无适配版本,或需定制编译(如开启 / 关闭功能),选源码编译:
1.克隆源码
cpp
# 克隆官方仓库(最新稳定版3.0.4)
git clone --depth 1 --branch 3.0.4 https://github.com/modelon-community/fmi-library.git
cd fmi-library
2.编译(分平台)
Linux/macOS
cpp
# 创建编译目录
mkdir build && cd build
# 配置CMake(指定安装路径,默认/usr/local)
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release
# 编译(-j后接CPU核心数,加速编译)
make -j$(nproc) # Linux
# make -j$(sysctl -n hw.ncpu) # macOS
# 安装(需sudo,若指定/usr/local)
sudo make install
Windows(Visual Studio)
cpp
# 创建编译目录
mkdir build && cd build
# 配置CMake(指定VS版本,安装到C:\fmilib)
cmake .. -G "Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX=C:\fmilib -DCMAKE_BUILD_TYPE=Release
# 编译并安装
cmake --build . --config Release --target INSTALL
编译完成后,C:\fmilib 下会生成 include(头文件)、lib(库文件)、bin(动态库)。
4.基本使用流程
该示例实现加载 FMU、初始化、设置输入变量、执行仿真步、读取输出、释放资源的全流程,包含错误处理以提升稳定性。
cpp
#include <fmilib.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
// 1. 配置参数与变量定义
const char* fmu_path = "your_model.fmu"; // 替换为你的FMU文件路径
fmi_import_context_t* ctx = NULL;
fmi2_import_t* fmu = NULL;
fmi2_component_t comp = NULL;
fmi2_status_t status;
double current_time = 0.0;
double step_size = 0.1; // 仿真步长
int step_count = 10; // 总仿真步数
// 2. 创建FMU导入上下文(核心句柄,管理全局资源)
ctx = fmi_import_create_context();
if (!ctx) {
fprintf(stderr, "错误:创建FMU上下文失败\n");
return 1;
}
// 3. 加载FMU并解析(自动识别FMI版本,支持1.0/2.0/3.0)
fmu = fmi2_import_parse_xml(ctx, fmu_path, NULL);
if (!fmu) {
fprintf(stderr, "错误:加载FMU失败,路径是否正确?\n");
fmi_import_free_context(ctx);
return 1;
}
// 4. 实例化FMU(创建模型实例,指定实例名和日志回调)
comp = fmi2_import_instantiate(fmu, "FMU_Instance", fmi2_true, fmi2_false, NULL);
if (!comp) {
fprintf(stderr, "错误:实例化FMU失败\n");
fmi2_import_free(fmu);
fmi_import_free_context(ctx);
return 1;
}
// 5. 初始化仿真(设置初始时间、启动协同仿真)
status = fmi2_import_setup_experiment(fmu, comp, fmi2_false, 0.0, 0.0, fmi2_false, 0.0);
status = fmi2_import_enter_initialization_mode(fmu, comp);
status = fmi2_import_exit_initialization_mode(fmu, comp);
if (status != fmi2_status_ok) {
fprintf(stderr, "错误:初始化FMU失败,状态码:%d\n", status);
goto cleanup;
}
// 6. 循环执行仿真步
for (int i = 0; i < step_count; i++) {
// 6.1 设置输入变量(示例:设置名为"input_speed"的实数变量值为5.0)
const char* input_var = "input_speed";
fmi2_value_reference_t vr_input;
if (fmi2_import_get_variable_vr(fmu, input_var, &vr_input) != fmi2_status_ok) {
fprintf(stderr, "错误:未找到变量%s\n", input_var);
goto cleanup;
}
fmi2_import_set_real(fmu, comp, &vr_input, 1, &(double){5.0});
// 6.2 执行仿真步
status = fmi2_import_do_step(fmu, comp, current_time, step_size, fmi2_true);
if (status != fmi2_status_ok) {
fprintf(stderr, "错误:执行仿真步%d失败,状态码:%d\n", i, status);
goto cleanup;
}
// 6.3 读取输出变量(示例:读取名为"output_torque"的实数变量)
const char* output_var = "output_torque";
fmi2_value_reference_t vr_output;
double output_value;
if (fmi2_import_get_variable_vr(fmu, output_var, &vr_output) != fmi2_status_ok) {
fprintf(stderr, "错误:未找到变量%s\n", output_var);
goto cleanup;
}
fmi2_import_get_real(fmu, comp, &vr_output, 1, &output_value);
// 6.4 打印结果
current_time += step_size;
printf("时间:%.2f | %s:%.4f\n", current_time, output_var, output_value);
}
cleanup:
// 7. 释放资源(严格按照"实例→FMU→上下文"的顺序)
if (comp) fmi2_import_terminate(fmu, comp);
if (comp) fmi2_import_free_instance(fmu, comp);
if (fmu) fmi2_import_free(fmu);
if (ctx) fmi_import_free_context(ctx);
return 0;
}
5.实项目示例
以下提供基于 fmilib 的工业级真实项目示例(汽车发动机冷却系统 FMU 仿真),包含完整的项目结构、模块化代码、编译脚本和工程化最佳实践,贴近实际开发场景(如配置管理、日志封装、批量变量处理、错误码标准化)。
5.1.项目背景
场景:对某汽车发动机冷却系统的 FMU 模型(FMI 2.0 协同仿真模式)进行离线仿真,模拟不同环境温度下冷却液温度、水泵转速的动态变化,输出仿真结果到 CSV 文件,用于验证模型的工程合理性。
5.2.项目结构
cpp
engine_cooling_sim/
├── config/
│ └── sim_config.h # 仿真配置(FMU路径、步长、变量名等)
├── src/
│ ├── fmu_simulator.c # FMU仿真核心逻辑(封装为函数)
│ ├── fmu_simulator.h # 头文件
│ └── main.c # 主程序(入口、流程控制、结果输出)
├── build.sh # 编译脚本
├── README.md # 说明文档
└── data/ # 输出目录(自动生成)
└── sim_result.csv # 仿真结果文件
5.3.核心代码实现
1.配置文件 config/sim_config.h
集中管理仿真参数,便于工程维护:
cpp
#ifndef SIM_CONFIG_H
#define SIM_CONFIG_H
// FMU路径(根据实际路径修改)
#define FMU_PATH "./EngineCoolingSystem.fmu"
// 仿真参数
#define SIM_START_TIME 0.0 // 起始时间 (s)
#define SIM_END_TIME 300.0 // 结束时间 (s)
#define SIM_STEP_SIZE 1.0 // 仿真步长 (s)
#define ENV_TEMP_BASE 25.0 // 基础环境温度 (℃)
#define ENV_TEMP_RATE 0.1 // 环境温度上升速率 (℃/s)
// 模型变量名(需与FMU的modelDescription.xml一致)
#define VAR_ENV_TEMP "environment_temperature" // 输入:环境温度
#define VAR_COOLANT_TEMP "coolant_temperature" // 输出:冷却液温度
#define VAR_PUMP_SPEED "water_pump_speed" // 输出:水泵转速
// 日志级别
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR
} LogLevel;
#endif // SIM_CONFIG_H
2.FMU 仿真核心封装 src/fmu_simulator.h
cpp
#ifndef FMU_SIMULATOR_H
#define FMU_SIMULATOR_H
#include <fmilib.h>
#include "config/sim_config.h"
// FMU仿真上下文(封装核心句柄,便于传递)
typedef struct {
fmi_import_context_t* ctx;
fmi2_import_t* fmu;
fmi2_component_t comp;
fmi2_value_reference_t vr_env_temp; // 环境温度变量引用
fmi2_value_reference_t vr_coolant_temp;// 冷却液温度变量引用
fmi2_value_reference_t vr_pump_speed; // 水泵转速变量引用
int is_initialized; // 初始化状态标记
} FMUSimContext;
// 初始化FMU仿真上下文
int fmu_sim_init(FMUSimContext* sim_ctx);
// 执行单步仿真
int fmu_sim_step(FMUSimContext* sim_ctx, double current_time, double step_size,
double env_temp, double* coolant_temp, double* pump_speed);
// 释放FMU仿真资源
void fmu_sim_cleanup(FMUSimContext* sim_ctx);
// 日志打印函数(封装,便于调试)
void log_print(LogLevel level, const char* format, ...);
#endif // FMU_SIMULATOR_H
3.FMU 仿真核心实现 src/fmu_simulator.c
cpp
#include "fmu_simulator.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
// 日志打印实现
void log_print(LogLevel level, const char* format, ...) {
const char* level_str[] = {"DEBUG", "INFO", "WARN", "ERROR"};
va_list args;
fprintf(stderr, "[%s] ", level_str[level]);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fprintf(stderr, "\n");
}
// 获取变量引用(封装重复逻辑)
static int get_variable_vr(fmi2_import_t* fmu, const char* var_name, fmi2_value_reference_t* vr) {
if (fmi2_import_get_variable_vr(fmu, var_name, vr) != fmi2_status_ok) {
log_print(LOG_ERROR, "变量%s的引用获取失败", var_name);
return -1;
}
log_print(LOG_DEBUG, "变量%s的引用获取成功:%d", var_name, *vr);
return 0;
}
// 初始化FMU仿真上下文
int fmu_sim_init(FMUSimContext* sim_ctx) {
// 初始化上下文结构体
memset(sim_ctx, 0, sizeof(FMUSimContext));
sim_ctx->is_initialized = 0;
// 1. 创建FMU导入上下文
sim_ctx->ctx = fmi_import_create_context();
if (!sim_ctx->ctx) {
log_print(LOG_ERROR, "创建FMU上下文失败");
return -1;
}
// 2. 加载并解析FMU
sim_ctx->fmu = fmi2_import_parse_xml(sim_ctx->ctx, FMU_PATH, NULL);
if (!sim_ctx->fmu) {
log_print(LOG_ERROR, "加载FMU失败(路径:%s)", FMU_PATH);
fmu_sim_cleanup(sim_ctx);
return -1;
}
log_print(LOG_INFO, "FMU加载成功:%s", FMU_PATH);
// 3. 获取变量引用
if (get_variable_vr(sim_ctx->fmu, VAR_ENV_TEMP, &sim_ctx->vr_env_temp) != 0 ||
get_variable_vr(sim_ctx->fmu, VAR_COOLANT_TEMP, &sim_ctx->vr_coolant_temp) != 0 ||
get_variable_vr(sim_ctx->fmu, VAR_PUMP_SPEED, &sim_ctx->vr_pump_speed) != 0) {
fmu_sim_cleanup(sim_ctx);
return -1;
}
// 4. 实例化FMU
sim_ctx->comp = fmi2_import_instantiate(
sim_ctx->fmu,
"EngineCoolingSim", // 实例名
fmi2_true, // 可见性(visible)
fmi2_false, // 交互性(interactive)
NULL // 日志回调(使用默认)
);
if (!sim_ctx->comp) {
log_print(LOG_ERROR, "FMU实例化失败");
fmu_sim_cleanup(sim_ctx);
return -1;
}
// 5. 初始化仿真
fmi2_status_t status = fmi2_import_setup_experiment(
sim_ctx->fmu, sim_ctx->comp,
fmi2_false, 0.0, SIM_START_TIME, fmi2_false, SIM_END_TIME
);
if (status != fmi2_status_ok) {
log_print(LOG_ERROR, "设置仿真实验失败(状态码:%d)", status);
fmu_sim_cleanup(sim_ctx);
return -1;
}
status = fmi2_import_enter_initialization_mode(sim_ctx->fmu, sim_ctx->comp);
status = fmi2_import_exit_initialization_mode(sim_ctx->fmu, sim_ctx->comp);
if (status != fmi2_status_ok) {
log_print(LOG_ERROR, "FMU初始化失败(状态码:%d)", status);
fmu_sim_cleanup(sim_ctx);
return -1;
}
sim_ctx->is_initialized = 1;
log_print(LOG_INFO, "FMU仿真上下文初始化完成");
return 0;
}
// 执行单步仿真
int fmu_sim_step(FMUSimContext* sim_ctx, double current_time, double step_size,
double env_temp, double* coolant_temp, double* pump_speed) {
if (!sim_ctx->is_initialized) {
log_print(LOG_ERROR, "FMU未初始化,无法执行仿真步");
return -1;
}
// 1. 设置输入变量(环境温度)
fmi2_status_t status = fmi2_import_set_real(
sim_ctx->fmu, sim_ctx->comp,
&sim_ctx->vr_env_temp, 1, &env_temp
);
if (status != fmi2_status_ok) {
log_print(LOG_ERROR, "设置环境温度失败(时间:%.2f)", current_time);
return -1;
}
// 2. 执行仿真步
status = fmi2_import_do_step(
sim_ctx->fmu, sim_ctx->comp,
current_time, step_size, fmi2_true
);
if (status != fmi2_status_ok) {
log_print(LOG_ERROR, "执行仿真步失败(时间:%.2f,状态码:%d)", current_time, status);
return -1;
}
// 3. 读取输出变量(冷却液温度 + 水泵转速)
status = fmi2_import_get_real(
sim_ctx->fmu, sim_ctx->comp,
&sim_ctx->vr_coolant_temp, 1, coolant_temp
);
if (status != fmi2_status_ok) {
log_print(LOG_ERROR, "读取冷却液温度失败(时间:%.2f)", current_time);
return -1;
}
status = fmi2_import_get_real(
sim_ctx->fmu, sim_ctx->comp,
&sim_ctx->vr_pump_speed, 1, pump_speed
);
if (status != fmi2_status_ok) {
log_print(LOG_ERROR, "读取水泵转速失败(时间:%.2f)", current_time);
return -1;
}
log_print(LOG_DEBUG, "仿真步完成(时间:%.2f,环境温度:%.2f,冷却液温度:%.2f,水泵转速:%.2f)",
current_time + step_size, env_temp, *coolant_temp, *pump_speed);
return 0;
}
// 释放FMU仿真资源
void fmu_sim_cleanup(FMUSimContext* sim_ctx) {
if (sim_ctx->is_initialized && sim_ctx->comp) {
fmi2_import_terminate(sim_ctx->fmu, sim_ctx->comp);
fmi2_import_free_instance(sim_ctx->fmu, sim_ctx->comp);
log_print(LOG_INFO, "FMU实例已释放");
}
if (sim_ctx->fmu) {
fmi2_import_free(sim_ctx->fmu);
log_print(LOG_INFO, "FMU句柄已释放");
}
if (sim_ctx->ctx) {
fmi_import_free_context(sim_ctx->ctx);
log_print(LOG_INFO, "FMU上下文已释放");
}
sim_ctx->is_initialized = 0;
}
4.主程序 src/main.c
负责流程控制、结果输出到 CSV:
cpp
#include "fmu_simulator.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h> // 用于创建目录(Linux/macOS)
// 创建输出目录(跨平台简化版)
static int create_output_dir() {
#ifdef _WIN32
// Windows:md命令
system("mkdir data 2>nul");
#else
// Linux/macOS:mkdir -p
system("mkdir -p data");
#endif
return 0;
}
int main() {
FMUSimContext sim_ctx;
FILE* csv_file = NULL;
double current_time = SIM_START_TIME;
double env_temp, coolant_temp, pump_speed;
// 1. 初始化日志
log_print(LOG_INFO, "发动机冷却系统FMU仿真启动");
// 2. 创建输出目录并打开CSV文件
if (create_output_dir() != 0) {
log_print(LOG_ERROR, "创建输出目录失败");
return -1;
}
csv_file = fopen("./data/sim_result.csv", "w");
if (!csv_file) {
log_print(LOG_ERROR, "打开CSV文件失败");
return -1;
}
// 写入CSV表头
fprintf(csv_file, "time,env_temp,coolant_temp,pump_speed\n");
// 3. 初始化FMU仿真上下文
if (fmu_sim_init(&sim_ctx) != 0) {
log_print(LOG_ERROR, "FMU仿真初始化失败,退出程序");
fclose(csv_file);
return -1;
}
// 4. 循环执行仿真
log_print(LOG_INFO, "开始仿真(起始时间:%.1fs,结束时间:%.1fs,步长:%.1fs)",
SIM_START_TIME, SIM_END_TIME, SIM_STEP_SIZE);
while (current_time < SIM_END_TIME) {
// 计算当前环境温度(线性上升)
env_temp = ENV_TEMP_BASE + (current_time * ENV_TEMP_RATE);
// 执行单步仿真
if (fmu_sim_step(&sim_ctx, current_time, SIM_STEP_SIZE, env_temp, &coolant_temp, &pump_speed) != 0) {
log_print(LOG_ERROR, "仿真步执行失败,终止仿真");
break;
}
// 写入CSV
fprintf(csv_file, "%.1f,%.2f,%.2f,%.2f\n",
current_time + SIM_STEP_SIZE, env_temp, coolant_temp, pump_speed);
// 更新时间
current_time += SIM_STEP_SIZE;
// 每10步打印进度
if ((int)(current_time / SIM_STEP_SIZE) % 10 == 0) {
log_print(LOG_INFO, "仿真进度:%.1f%%(当前时间:%.1fs)",
(current_time / SIM_END_TIME) * 100, current_time);
}
}
// 5. 收尾工作
fmu_sim_cleanup(&sim_ctx);
if (csv_file) {
fclose(csv_file);
log_print(LOG_INFO, "仿真结果已保存到 ./data/sim_result.csv");
}
log_print(LOG_INFO, "发动机冷却系统FMU仿真结束");
return 0;
}
5.4.编译脚本 build.sh
cpp
#!/bin/bash
# 编译脚本(Linux/macOS)
set -e # 出错时终止脚本
# 编译参数
CC=gcc
CFLAGS="-Wall -Wextra -I./config -I/usr/include/fmilib -O2"
LDFLAGS="-lfmilib -lm"
SRC_FILES="src/main.c src/fmu_simulator.c"
OUTPUT="engine_cooling_sim"
# 编译
echo "开始编译..."
$CC $CFLAGS $SRC_FILES -o $OUTPUT $LDFLAGS
# 检查编译结果
if [ -f "./$OUTPUT" ]; then
echo "编译成功!可执行文件:./$OUTPUT"
echo "运行命令:./$OUTPUT"
else
echo "编译失败!"
exit 1
fi
5.5.运行说明
编译与运行:
cpp
# 赋予脚本执行权限
chmod +x build.sh
# 编译
./build.sh
# 运行仿真
./engine_cooling_sim
- 控制台打印仿真日志(进度、调试信息);
data/sim_result.csv包含时间、环境温度、冷却液温度、水泵转速的仿真数据,可导入 Excel/Matlab 分析。
6.总结
fmilib是 FMI 标准的权威 C 语言实现,为开发者提供了与 FMU 模型交互的底层接口。无论您是需要在 C/C++ 应用中集成第三方模型,还是开发自己的 FMI 兼容工具,fmilib 都是一个可靠的选择。
相关项目
- FMI++:基于 C++ 的 FMI 高级封装库,提供更便捷的面向对象接口
- PyFMI/FMPy:Python 绑定,便于在 Python 环境中使用 FMU
- MATLAB FMI Toolbox:MathWorks 官方支持的 FMI 接口