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

Создание и подключение пользовательского устройства в Engee

Введение

В этом примере рассматривается создание собственного пользовательского пакета поддержи устройства для работы в Engee через при помощи Engee.Интеграции.
Такой подход позволяет расширять стандартные возможности среды и подключать пользовательскую логику, написанную на Python.

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

  • подключается к Engee через механизм расширений;
  • предоставляет собственные методы;
  • может вызываться из сценариев и использоваться в модели;
  • применяется в схеме Engee как пользовательский функциональный блок.

Этот подход подходит для задач, где необходимо:

  • реализовать нестандартную логику устройства;
  • подключить внешние вычисления;
  • добавить методы чтения, записи, обработки данных;
  • организовать собственный интерфейс между Python и Engee.

Что такое пользовательское устройство

Пользовательский пакет поддержки устройств в Engee — это расширяемый объект, который описывается в виде Python-класса и подключается через Engee.Интеграции.

Такой класс должен быть совместим с инфраструктурой менеджера устройств Engee и наследоваться от базового класса:

BaseDevice

Это дает возможность:

  • регистрировать устройство в Engee;
  • автоматически обнаруживать его методы;
  • вызывать пользовательские функции из среды;
  • использовать устройство как источник логики в модели.

Общая структура пакета поддержки

Пакет поддержки состоит из нескольких уровней:

  1. Пользовательское устройство
    Описывает конкретную логику устройства и его методы.

  2. Python-модуль на компьютере пользователя
    Содержит реализацию класса устройства и его функций.

  3. Интеграционный слой Engee
    Обеспечивает подключение Python-модуля и взаимодействие между Engee и пользовательским кодом.

С практической точки зрения это означает следующее:

  • пользователь создает папку устройства;
  • внутри размещает Python-файл с реализацией класса;
  • затем загружает расширение в Engee;
  • после этого может создавать экземпляры устройства и вызывать его методы.

Требования к классу устройства

Пользовательское устройство должно быть реализовано как класс, наследуемый от BaseDevice.

Обычно в таком классе определяются:

  • конструктор:

    __init__()
    
  • деструктор:

    __del__()
    
  • один или несколько пользовательских методов:

    custom_method_1()
    custom_method_2()
    ...
    custom_method_N()
    

Именно эти методы затем становятся доступны для вызова из Engee.


Подготовка и структура каталогов

1. Создание каталога расширения

Для начала необходимо подготовить папку расширения, внутри которой будут размещаться пользовательские устройства.

Самый минимальный пример структуры каталогов:

D:\dev_devices\extension\
└── devices\
    └── tofile\
        └── tofile.py

Где:

  • extension — корневая папка пакета поддержки;
  • devices — каталог пользовательских устройств;
  • tofile — папка конкретного устройства;
  • tofile.py — Python-файл с реализацией класса устройства.

Названия каталогов можно выбирать свои, но их лучше делать короткими и понятными.


Создание Python-класса устройства

2. Базовая реализация пользовательского устройства

Внутри файла tofile.py необходимо создать класс, наследуемый от BaseDevice.

Пример базовой заготовки:

from devices.base_device import BaseDevice

class Tofile(BaseDevice):
    def __init__(self) -> None:
        pass

    def __del__(self) -> None:
        pass

Эта минимальная структура уже позволяет Engee обнаружить пользовательский класс как устройство.


3. Добавление пользовательских методов

После создания класса необходимо определить методы, которые должны вызываться из Engee.

Можно реализовать пользовательские функции, например:

  • получение результата служебной функции;
  • вычисление выражения;
  • получение времени хоста;
  • запись значения в файл.

Ниже приведен типовой вариант класса с несколькими методами.

from devices.base_device import BaseDevice
import time

class Tofile(BaseDevice):
    def __init__(self) -> None:
        pass

    def __del__(self) -> None:
        pass

    def solve_equation(self, x: float, y: float, z: float) -> float:
        return x * y - z

    def get_host_time(self) -> str:
        host_time = time.localtime()
        return str(host_time)

4. Реализация метода записи числа в файл

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

Для этого можно добавить метод следующего вида:

from devices.base_device import BaseDevice

class Tofile(BaseDevice):
    def __init__(self) -> None:
        pass

    def __del__(self) -> None:
        pass

    def write_number(self, file_path:str, number:float) -> str:
        try:
            with open(file_path, 'a', encoding='utf-8') as f:
                f.write(f"{number}\n")
            return "OK"
        except Exception as e:
            return  f"{e}"

Что делает этот метод

Метод write_number(...):

  • принимает путь к файлу;
  • принимает число;
  • открывает файл в режиме добавления;
  • записывает значение в новую строку;
  • возвращает строковый результат выполнения.

Это удобный шаблон для простого обмена данными между моделью Engee и локальной файловой системой.


Подключение пакета поддержки в Engee

5. Создание скрипта подключения

После подготовки Python-модуля необходимо подключить расширение в Engee.

Для этого импортируются служебные пространства имен и вызывается загрузка расширения.

In [ ]:
using Main.EngeeDeviceManager.Devices.UTILS
using Main.EngeeDeviceManager.UTILS_API
In [ ]:
module_path = "D:\\dev_devices\\extension";
utils = UTILS.Utils()
UTILS_API.loadExtension(utils, module_path)

Что делает этот код

module_path = "D:\\dev_devices\\extension";

Задает путь к корневой папке пакета поддержки.

utils = UTILS.Utils()

Создает вспомогательный объект, необходимый для загрузки расширения.

UTILS_API.loadExtension(utils, module_path)

Подключает пользовательский пакет поддержки к Engee.

После успешного выполнения этой команды пользовательское устройство становится доступным для использования в среде.

6. Перезапуск менеджера устройств Engee

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

Для этого используется команда:

In [ ]:
engee.package.start("Engee-Device-Manager")

Проверка успешного подключения

7. Открытие окна состояния подключения

После загрузки расширения необходимо проверить, что Engee успешно обнаружил пользовательское устройство.

Признаками успешного подключения являются сообщения следующего типа:

  • соединение с сервером установлено;
  • класс найден;
  • класс наследуется от BaseDevice;
  • методы класса обнаружены;
  • типы аргументов и возвращаемых значений корректно распознаны;
  • расширение успешно загружено.

Что важно проверить в логах

После загрузки расширения необходимо убедиться, что Engee корректно распознал:

image.png

Класс устройства, например:

Класс найден: Tofile, наследуется от BaseDevice

Методы устройства, например:

Метод: write_number
Метод: __init__
Метод: __del__

Аргументы метода, например:

Аргумент: file_path Тип: str
Аргумент: number Тип: float

Возвращаемый тип, например:

Возвращаемые значения: str

Если эти данные отображаются корректно, значит пакет поддержки загружен правильно и устройство готово к использованию.


Использование устройства в скрипте Engee

8. Создание экземпляра пользовательского устройства

После загрузки расширения можно создать экземпляр пользовательского класса.

Пример:

In [ ]:
myfile = TOFILE.Tofile()

Здесь:

  • myfile — экземпляр пользовательского класса;
  • TOFILE — пространство или модуль пользовательского устройства;
  • Tofile() — конструктор класса.

После выполнения этой команды создается рабочий экземпляр устройства, методы которого можно вызывать из Engee.


9. Вызов пользовательских методов

После создания экземпляра можно вызывать методы устройства напрямую.

Например:

In [ ]:
myfile.write_number("D:\\dev_devices\\extension\\test.txt", 3.1415)

После выполнения в указанный файл будет записано число 3.1415.


Использование пользовательского устройства в модели

10. Создание пользовательского блока пакета поддержки

После успешного подключения устройства можно использовать его в модели Engee.
Для этого в блок Engee Function добавим следующий код:

package_dir = "/internal_persistent_vol/support_packages/locations/Engee-Device-Manager/EngeeDeviceManager.jl"

include("$(package_dir)/src/EngeeDeviceManager.jl")


using .EngeeDeviceManager
using .EngeeDeviceManager.Devices.TOFILE

struct Block <: AbstractCausalComponent
    myfile::TOFILE.Tofile
    function Block()
      myfile = TOFILE.Tofile()
      new(myfile)
    end
end

function (c::Block)(t::Real, number)
  c.myfile.write_number(path,number)
end

function terminate!(c::Block)
  return nothing
end

number - значение, которые мы будем получать из входного порта

path - параметр, получаемый из маски блока.

Модель примера будет выглядеть следующим образом:

image.png

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

11. Результаты выполнения модели

После запуска модели в указанной директории ПК мы получим файл test1.txt со значениями синусоиды. Файл можно загрузить и проанализировать скрипте:

In [ ]:
function TxtToVec(Path::String)
    txt = open(io->read(io, String), Path);
    data_str = "[" * replace(txt, "\n" => ",") * "]";
    return eval(Meta.parse(data_str))
end

txt_content = TxtToVec("$(@__DIR__)/test1.txt");

gr(format=:png)
plot(txt_content)
Out[0]:
No description has been provided for this image

График значений из файла явно носит синусоидальный характер с заданными параметрами, следовательно, в файл записываются все необходимые данные. Теперь блок пользовательского пакета поддержки можно включить в пользовательскую библиотеку блоков, например tofile.nglib

Практические рекомендации

1. Соблюдать простую структуру класса

Лучше начинать с минимального набора:

  • __init__
  • __del__
  • 1–2 пользовательских метода

После проверки работоспособности можно постепенно расширять функциональность.


2. Явно указывать типы аргументов

Обязательно указывать типы входных параметров и возвращаемого значения, например:

def write_number(self, file_path: str, number: float) -> str:

Это обеспечивает определение типов данных между методами пакета в Python и Julia-интерфейсом в Engee.


3. Обрабатывать исключения

Если метод взаимодействует с файлами, внешними ресурсами или вычислениями, полезно добавлять try/except, чтобы ошибки не приводили к аварийному завершению.


4. Начинать с простых тестов

Перед интеграцией в модель удобно сначала проверить:

  • создание экземпляра класса;
  • вызов метода;
  • возвращаемое значение.

И только после этого подключать устройство к рабочей схеме.


Заключение

В результате выполнения этой инструкции создается собственное пользовательское устройство для Engee, реализованное на Python и подключаемое через Engee.Интеграции.

Выполненная настройка позволяет:

  • создавать пользовательские классы устройств;
  • добавлять собственные методы;
  • подключать Python-логику в Engee;
  • использовать расширение в сценариях и моделях;
  • интегрировать нестандартный функционал в рабочий процесс.

Такой механизм удобно использовать как основу для:

  • файлового обмена;
  • внешних вычислений;
  • взаимодействия с оборудованием;
  • интеграции с пользовательскими библиотеками;
  • реализации специализированной прикладной логики.