AnyMath 文档

模型生成、装配、加载和启动

用于管理模型的操作的方法

对于独立模式,实现四个就足够了 方法 BaseTarget:

  1. generate_executable_code -准备目标平台的草案。

  2. compile_model -组装项目并获得工件。

  3. upload_model -将工件转移到设备或准备发射。

  4. start_model -如果平台需要单独启动,请确认启动或启动工件。

这些方法由基础结构调用 目标硬件顺序,因此每个方法都必须保存下一步所需的状态:项目的路径,构建目录和固件,可执行文件的名称,加载程序设置等。

基本Api

目标代码中使用的主要模块:

  • targets.base_target -目标类的契约和管理模型操作的方法(启动,运行,停止)。

  • targets.base_models -目标接收和返回的数据模型。

  • targets.code_info -模型的生成C代码的低级描述。

以下Api用于独立执行模式的目标代码:

  • BaseTarget 设置所需的目标接口。 自定义类是从它继承的。 即使目标不支持交互模式,方法 start_streamchange_param 它们仍然应该实现,但要针对独立的执行模式,它们应该返回一个可以理解的错误。

  • EngeeModel 包含模型ID。 在实践中,它最常用 model.name:用于命名项目目录、草图、二进制文件或固件。

  • ModelSettings 包含启动模型的设置。 要生成一个项目,它们尤其重要 model_settings.cmimodel_settings.is_ext_mode. 本章讨论独立执行模式场景,所以 is_ext_mode 必须是 False 或者应该明确拒绝。

  • ModelCode 包含生成的模型代码。 对于C-靶标,使用 model_code.c_code:它有源文件的字典和一个字段 c_code_info 用元数据。

  • TargetResponse -靶向方法的标准响应。 对于独立的执行模式方法,返回就足够了 TargetResponse(detail="…​"),在哪里 detail 简要描述已完成的阶段。

  • CMIParser 从读取*EDM-Target*块参数 model_settings.cmi. 方法 get_target_block_options 将*EDM-Target*块掩码的值转换为用户平台设置的Pydantic模型。

  • create_codegen_info 转换详细的 CodeInfo 以更方便的方式进行模板 CodeGenInfo:C函数名称 init/step/terminate、基期、步骤函数列表、模型停止时间和其他运行时参数。

  • render_template_to_file 在项目文件中生成Jinja2模板:例如, main.c, .ino, CMakeLists.txtMakefile.

  • recreate_dir 删除并重新创建项目目录。 这对于纯生成很方便,但仅对目标创建的目录使用此API。

  • store_model_src 将模型的C源保存在项目中。 生成的 AnyMath main.c 它被跳过,因为目标从模板创建自己的入口点。

  • store_filed_sources 保存嵌入在*C函数*模型块中的用户源文件。 如果目标平台需要支持来自模型的自定义C代码,请使用它。

  • CMakeWrapper -准备包装CMake组装。 如果目标平台正在经历,则可以使用它 CMakeLists.txt. 他们通常为Arduino,制造商的SDK或他们自己的构建系统创建自己的构建器类。

方法 generate_executable_code 绑定 代码生成 AnyMath 具有自定义平台。 它不应该编译项目。 他的任务是在yandex上创建一个完整的项目。 磁盘,然后他将能够组装 compile_model.

该方法通常执行以下操作:

  1. 储蓄 model_settings, model_code 以及后续步骤将需要的其他数据。

  2. 通过读取*EDM-Target*块参数 CMIParser.

  3. 检查是否选择了支持的执行模式。

  4. 创建一个干净的项目目录。

  5. 皈依者 model_code.c_code.c_code_infoCodeGenInfo.

  6. Renderit运行时模板: main.c, .ino, CMakeLists.txt, Makefile 或IDE项目文件。

  7. 通过以下方式保存模型的C源 store_model_src.

  8. 复制驱动程序、标头、布局文件、启动文件、SDK绑定和其他平台文件。

  9. 通过保存自定义*C函数*文件 store_filed_sources 如果他们得到支持。

  10. 申报表 TargetResponse(detail="generate_executable_code").

结构示例 generate_executable_code

def generate_executable_code(
    self,
    model: EngeeModel,
    model_settings: ModelSettings,
    model_code: ModelCode,
) -> TargetResponse:
    if model_settings.is_ext_mode:
        raise TargetException("MyTarget supports only independent execution.")

    self.model_settings = model_settings
    self.target_block = self.cmi_parser.get_target_block_options(
        model_settings.cmi,
        MyTargetBlock,
        self.__class__.__name__,
    )

    self.project_path = Path(self.target_block.codegen_folder) / model.name
    recreate_dir(self.project_path)

    project_src = self.project_path / "src"
    project_src.mkdir(parents=True, exist_ok=True)

    codegen_info = create_codegen_info(
        model_code.c_code.c_code_info,
        model_settings.is_ext_mode,
    )

    render_template_to_file(
        self.main_template,
        project_src / "main.c",
        codegen_info.model_dump(),
    )

    store_model_src(model_code.c_code, project_src, codegen_info)
    store_filed_sources(model_settings.cmi, project_src)

    return TargetResponse(detail="generate_executable_code")

Jinja2-模板

Jinja2是一个模板引擎:它获取一个带有替换和控制结构的文本文件,获取一个值字典,并生成一个最终文件。 在targets中,需要Jinja2来生成依赖于特定模型的C/C++/CMake代码。

模板是需要的,因为模型的函数名称、基期、步骤函数列表和停止时间是事先未知的。 它们仅在代码生成后出现。 AnyMath . 目标通过 CodeGenInfo 并将它们传递给模板。

模板通常使用:

  • {{ name }} -价值的替代;

  • {% for item in items %} …​ {% endfor %} -循环;

  • {% if condition %} …​ {% endif %} -条件片段。

模板应该只描述平台绑定。 模型代码已经在它保存的文件中。 store_model_src.

模板片段示例 main.c

#include "{{ model_name }}.h"

static unsigned long step_number = 0;

int main(void)
{
    platform_timer_init({{ base_rate_us }}UL);
    {{ init.cname }}();

    while (1) {
        unsigned long start_time = platform_time_us();

{% for step in steps %}
        if ((step_number % {{ step.base_rate_scale }}UL) == 0UL) {
            {{ step.cname }}();
        }
{% endfor %}

        step_number++;

{% if stop_time %}
        if ((step_number * {{ base_rate_us }}UL) > {{ stop_time_us }}UL) {
            {{ terminate.cname }}();
            break;
        }
{% endif %}

        platform_wait_until_next_tick(start_time, {{ base_rate_us }}UL);
    }

    return 0;
}

传递给模板的值示例

codegen_info = create_codegen_info(
    model_code.c_code.c_code_info,
    model_settings.is_ext_mode,
)

context = codegen_info.model_dump()
render_template_to_file("templates/main.c", "build/src/main.c", context)

运行时模板

独立执行模式的运行时模板应包含以下信息

*程序或固件的入口点在哪里; *当模型初始化函数被调用时; *如何调用所有step函数; *基期如何维持; *如何处理多个计算步骤; *发生超限时会发生什么; *如何处理有限模拟时间; *当模型完成函数被调用时; *哪些平台驱动程序和标头可用于用户的C代码。

本文更详细地讨论了模型的调度程序和执行。 独立模式下的模型执行.

Python目标代码创建项目,模型的运行时行为由C/C++运行时模板设置。

组装

方法 compile_model 它启动了一个基于平台的工具链,并将生成的项目转换为可以下载或运行的工件。

可能的选择:

  • arduino-cli compile; *CMake和交叉编译器; *制作或忍者; *CLI/SDK制造商; *构建本地可执行文件; *固件组装 .hex, .bin, .elf. compile_model 它必须验证项目是否已创建,运行构建,处理工具链错误,并将最终工件的路径保存在目标对象中。

通过装配的示例 CMakeWrapper

def compile_model(self, model: EngeeModel) -> TargetResponse:
    if self.project_path is None or not self.project_path.exists():
        raise TargetException(
            "Project was not generated. Call generate_executable_code first."
        )

    self.cmake.build_all(str(self.project_path))
    self.artifact_path = self.project_path / "build" / "my_firmware.elf"

    if not self.artifact_path.exists():
        raise TargetException(f"Build artifact not found: {self.artifact_path}")

    return TargetResponse(detail="compile_model")

调用您自己的邮件导入器的示例

def compile_model(self, model: EngeeModel) -> TargetResponse:
    self.builder.build_project(model.name)
    self.artifact_path = self.builder.get_artifact_path(model.name)
    return TargetResponse(detail="compile_model")

装载量

方法 upload_model 将构建结果传输到应该执行的位置。 对于微控制器,这通常是通过编程器固件。 对于Linux设备,通过SSH复制。 对于本地应用程序,如果二进制文件已经在正确的机器上,则此步骤可能不会执行任何操作。

将下载与构建分开是很重要的:自定义工具链错误应该发生在 compile_model,并且与设备,编程器或远程主机的通信错误在 upload_model.

固件下载示例

def upload_model(self, model: EngeeModel) -> TargetResponse:
    if self.artifact_path is None or not self.artifact_path.exists():
        raise TargetException("Build artifact not found. Compile model first.")

    self.loader.flash(self.artifact_path)
    return TargetResponse(detail="upload_model")

没有单独下载的本地应用程序示例

def upload_model(self, model: EngeeModel) -> TargetResponse:
    return TargetResponse(detail="upload_model")

推出

方法 start_model 加载后调用。 在独立执行模式中,有两种可能的选择:

*下载后固件已经启动,方法只返回成功响应。; *收集的工件是一个应用程序,方法应该启动进程。

如果进程是从 start_model 目标应该保存进程描述符,以便以后 stop_model 我本可以阻止他的。 如果平台不支持在独立模式下远程停止模型, stop_model 它只能返回成功的响应,但应该在目标的自述文件中描述此行为。

启动后运行的固件示例

async def start_model(self, model: EngeeModel) -> TargetResponse:
    return TargetResponse(detail="start_model")

启动本地进程的示例

async def start_model(self, model: EngeeModel) -> TargetResponse:
    if self.artifact_path is None or not self.artifact_path.exists():
        raise TargetException("Executable not found. Compile model first.")

    self.current_process = subprocess.Popen([str(self.artifact_path)])
    return TargetResponse(detail="start_model")

清单

要以独立执行模式为目标,请检查:

**EDM-Target*块正在通过以下方式成功读取 CMIParser; *项目在目标目录中创建; *运行时模板使用 CodeGenInfo,而不是模型函数的硬编码名称; * store_model_src 将模型的源代码保存到预期位置; *驱动程序、标头和生成文件最终会出现在项目中; * compile_model 检查项目和最终工件。; * upload_model 不将上传与构建混合使用; * start_model 明确描述平台的行为:启动确认或进程启动; *Tulchain,加载和启动的错误变成了可以理解的目标异常。