fmilib: 一个FMI 标准的 C 语言实现库

目录

1.简介

2..FMU文件包

[2.1.FMU 文件的核心属性](#2.1.FMU 文件的核心属性)

[2.2.FMU 标准目录 / 文件结构](#2.2.FMU 标准目录 / 文件结构)

[2.3.核心文件 / 目录详解](#2.3.核心文件 / 目录详解)

[2.4.不同 FMI 模式的 FMU 结构差异](#2.4.不同 FMI 模式的 FMU 结构差异)

3.安装与集成

3.1.下载地址

3.2.包管理器安装(便捷,优先推荐)

3.3.源码编译安装(通用,适配所有平台)

4.基本使用流程

5.实项目示例

5.1.项目背景

5.2.项目结构

5.3.核心代码实现

[5.4.编译脚本 build.sh](#5.4.编译脚本 build.sh)

5.5.运行说明

6.总结

相关项目


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 文件的核心属性

  1. 格式本质 :后缀为 .fmu,但可直接用 ZIP 解压工具(如 WinRAR、7-Zip、unzip 命令)解压,解压后可见标准化目录结构。
  2. 核心目的:封装模型的 "描述信息 + 可执行逻辑 + 依赖资源",让不同仿真工具(如 Simulink、Dymola、OpenModelica)能无差别加载运行。
  3. 两种核心模式:FMU 分为「模型交换(ME)」和「协同仿真(CS)」,结构上的差异主要体现在二进制接口和 XML 描述中。

2.2.FMU 标准目录 / 文件结构

以下是 FMI 2.0/3.0 最典型的结构(FMI 1.0 仅目录命名略有差异,如 binariesbin):

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/

存放不同操作系统 / 架构的二进制执行库,是模型的 "运行逻辑" 核心:

  • Windowswin32/*.dll(32 位)、win64/*.dll(64 位);
  • Linuxlinux32/lib*.so(32 位)、linux64/lib*.so(64 位);
  • macOSdarwin64/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.下载地址

3.2.包管理器安装(便捷,优先推荐)

1.Linux 部分发行版

  • Arch Linux/AURyay -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:

vcpkg: 一款免费开源的C++包管理器

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 接口
相关推荐
小曹要微笑1 小时前
PCA9555 I/O扩展芯片驱动详解
c语言·单片机·嵌入式硬件·freertos·io扩展芯片·pca9555
zore_c1 小时前
【C语言】文件操作详解2(文件的顺序读写操作)
android·c语言·开发语言·数据结构·笔记·算法·缓存
Less is moree1 小时前
C语言文件操作中的读写模式
c语言
量子炒饭大师1 小时前
初探算法的魅力——【暴力枚举】
c语言·数据结构·c++·算法·动态规划
枫叶丹42 小时前
【Qt开发】Qt窗口(六) -> QMessageBox 消息对话框
c语言·开发语言·数据库·c++·qt·microsoft
缘三水2 小时前
【C语言】11.指针(1)
c语言·开发语言·指针
Miuney_MAX2 小时前
【电子电路】之Type-C正反插
c语言·开发语言
hefaxiang10 小时前
C语言常见概念(下)
c语言·开发语言
potato_may10 小时前
链式二叉树 —— 用指针构建的树形世界
c语言·数据结构·算法·链表·二叉树