Документация Engee

Базовый класс таргета и блок EDM-Target

Страница в процессе разработки.

Отличие таргета от устройства состоит в назначении класса: устройство обычно предоставляет пользовательские методы для блоков и скриптов, а таргет реализует работу модели на целевой платформе.

Базовый класс

Класс таргета наследуется от BaseTarget и реализует методы жизненного цикла модели.

Ниже приведен пример таргета с независимым режимом. Такой таргет генерирует и собирает проект, загружает или запускает результат, но не поддерживает интерактивный обмен с Engee во время выполнения модели. Поэтому методы start_stream и change_param, которые относятся к интерактивному выполнению, всегда возвращают ошибку с соответствующим сообщением.

from pathlib import Path
from typing import List, Optional, Union
from uuid import UUID

from pydantic import BaseModel

from devices.base_models import Result
from targets.base_models import EngeeModel, ModelCode, ModelSettings, TargetResponse
from targets.base_target import BaseTarget
from targets.exceptions import TargetException
from targets.utils.CMIParser import CMIParser
from targets.utils.CodeGen import recreate_dir, render_template_to_file, store_model_src
from targets.utils.CodeGenInfo import create_codegen_info


class MyTargetBlock(BaseModel):
    """Параметры Target-блока в модели Engee.

    Имена полей должны совпадать с именами параметров маски Target-блока.
    """

    port: str
    toolchain_path: str = "auto"
    codegen_folder: str = "."


class MyTarget(BaseTarget):
    """Таргет для платформы TODO: название платформы."""

    def __init__(self) -> None:
        self.cmi_parser = CMIParser()
        self.target_block: Optional[MyTargetBlock] = None
        self.model_settings: Optional[ModelSettings] = None

    def is_connected(self) -> bool:
        """Проверить доступность устройства или программатора."""
        # TODO: вызвать CLI/SDK/драйвер и вернуть фактический статус.
        return False

    def generate_executable_code(
        self,
        model: EngeeModel,
        model_settings: ModelSettings,
        model_code: ModelCode,
    ) -> TargetResponse:
        """Создать проект для целевой платформы из сгенерированного C-кода Engee."""
        self.model_settings = model_settings
        self.target_block = self.cmi_parser.get_target_block_options(
            model_settings.cmi,
            MyTargetBlock,
            self.__class__.__name__,
        )

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

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

        render_template_to_file(
            "templates/main.c",
            project_path / "main.c",
            codegen_info.model_dump(),
        )
        store_model_src(model_code.c_code, project_path, codegen_info)

        # TODO: скопировать драйверы, runtime, linker script, файлы SDK.
        # TODO: сгенерировать CMakeLists.txt/Makefile/project-файлы IDE.

        return TargetResponse(detail="generate_executable_code")

    def compile_model(self, model: EngeeModel) -> TargetResponse:
        """Собрать проект toolchain-ом платформы."""
        # TODO: запустить компилятор, CMake, arduino-cli, vendor CLI или SDK.
        # TODO: проверить, что выходной файл действительно создан.
        return TargetResponse(detail="compile_model")

    def upload_model(self, model: EngeeModel) -> TargetResponse:
        """Загрузить собранный артефакт на устройство."""
        # TODO: прошить .hex/.bin/.elf или скопировать исполняемый файл.
        return TargetResponse(detail="upload_model")

    async def start_model(self, model: EngeeModel) -> TargetResponse:
        """Запустить модель или подготовить runtime-соединение."""
        return TargetResponse(detail="start_model")

    async def start_stream(self, simulation_uuid: str) -> Result:
        """Запустить поток данных для интерактивного выполнения."""
        raise TargetException("MyTarget does not support interactive execution streaming.")

    async def change_param(
        self,
        block_key: Union[str, UUID],
        param: str,
        data: Union[int, float, bool, List[int], List[float], List[bool]],
    ) -> TargetResponse:
        """Изменить параметр модели во время выполнения."""
        raise TargetException("MyTarget does not support runtime parameter tuning.")

    async def stop_model(self) -> TargetResponse:
        """Остановить выполнение или освободить ресурсы runtime."""
        return TargetResponse(detail="stop_model")

Создание блока EDM-Target

Блок EDM-Target — это специальный блок на холсте модели Engee, который сообщает режиму Target Hardware, на какой платформе нужно запускать модель и с какими настройками. Когда пользователь выбирает запуск в режиме Target Hardware, Engee ищет в модели блок EDM-Target. Для каждого оборудования используется свой блок, и от добавленного на холст блока зависит, какое оборудование будет выбрано для запуска.

Для пользовательского таргета необходимо:

  1. Включить в библиотеку блоков пакета поддержки блок EDM-Target, предназначенный для пользовательской платформы.

  2. Реализовать в Python-классе таргета чтение параметров этого блока из CMI-модели.

CMI (Compiled Model Information) — это описание текущей модели, которое Engee передает таргету вместе со сгенерированным кодом. В нем есть список блоков, их параметры и служебные настройки запуска. Метод CMIParser.get_target_block_options находит блок EDM-Target нужного типа и преобразует его параметры в Python-объект.

Параметры блока EDM-Target описываются отдельной Pydantic-моделью. Имена полей этой модели должны совпадать с именами параметров маски блока EDM-Target в .nglib.

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

С примерами моделей (ArduinoUNOTargetBlock или ATmega328PTargetBlock) можно ознакомиться в директории с примерами.

Если в модели нет подходящего блока EDM-Target, то Engee при запуске в режиме Target Hardware не сможет выбрать пользовательский таргет и применить настройки. Если блок есть, но поля маски не совпадают с Pydantic-моделью, таргет не сможет корректно прочитать порт, путь к тулчейну, папку генерации или другие параметры платформы.

Ограничения для Pydantic-модели блока EDM-Target:

  • имена полей модели должны соответствовать именам параметров блока EDM-Target в .nglib/модели;

  • валидацию, нормализацию путей и преобразование enum-значений лучше делать сразу после чтения блока EDM-Target.

Маска блока EDM-Target в Engee

Блок EDM-Target создается на основе блока Subsystem, который содержит только связку блоков Ground на Terminator, чтобы блок не был исключен из модели.

arduino uno target block

Информация, требуемая для таргета, передается через поля маски блока, список параметров не фиксирован и определяется разработчиком таргета в зависимости от потребностей конкретной платформы.

arduino uno target block under mask

Для корректной работы режима Target Hardware в маске блока должен быть объявлен параметр с именем edm_target_name. Значение этого параметра должно соответствовать имени класса таргета, к которому будет обращаться среда. Этот параметр лучше сделать скрытым во избежание случайного изменения.

arduino uno target block edm target name