Разработка пользовательского пакета поддержки устройства
|
Страница в процессе разработки. |
Данное руководство описывает сценарий, в котором:
-
Разработанный на Python пользовательский пакет поддержки работает на локальном компьютере, и необходимо использовать его из Engee.
-
Также предполагается использование этого пакета поддержки в модели Engee и его вызов из блока Engee Function.
-
Эту задачу можно решить с помощью Engee.Интеграции.
Структура пакета поддержки устройства
Минимальный шаблон устройства размещается в директории devices:
my_extension/
devices/
mydevice/
__init__.py
mydevice.py
Пакет поддержки (устройство) должен располагаться в devices/<папка_устройства>/.
|
Схема именования:
devices/debug_device/ — папка устройства
├── __init__.py — from .debug_device import DebugDevice
└── debug_device.py — class DebugDevice(BaseDevice):
def ping(...) -> str:
Julia: EngeeDeviceManager.Devices.DEBUGDEVICE
Блок: using .EngeeDeviceManager.Devices.DEBUGDEVICE
dev = DEBUGDEVICE.DebugDevice()
Имя папки debug_device, имя класса DebugDevice и имя Julia-модуля DEBUGDEVICE связаны правилом преобразования регистра: snake_case → CamelCase (Python класс) → UPPER_CASE (Julia-модуль).
Класс устройства (Python)
Обязательные требования:
-
Наследование от
BaseDevice. -
Полные аннотации типов у публичных методов: аргументы, их типы и возвращаемое значение
return, если функция возвращает результат. -
Методы, предназначенные для вызова из блока, не должны начинаться с
_и__— такие методы не будут доступны из скриптов и блоков.
Пример класса устройства:
from devices.base_device import BaseDevice
from main_logger import MainLogger
class MyDeviceLogger(MainLogger):
pass
logger = MyDeviceLogger()
class MyDevice(BaseDevice):
def __init__(self) -> None:
pass
def connect(self, host: str, port: int) -> bool:
logger.info("Connect {}:{}", host, port)
return True
def read_value(self, channel: int) -> float:
return 12.34
Обратите внимание, что при наследовании от встроенного класса MainLogger появляется возможность реализовать мультиязычное логирование для пользовательского пакета поддержки. Например:
from collections import defaultdict
from typing import Any
from main_logger import MainLogger
class MyDeviceLogger(MainLogger):
def __init__(self) -> None:
self.tr: Any = defaultdict(dict)
self.tr = {
"Connect {}:{}": {
"ru": "Подключение к {}:{}",
},
"Read failed: {}": {
"ru": "Ошибка чтения: {}",
},
}
Пользовательское устройство добавляется в Engee.Интеграции, синхронизируется c Engee и после этого может использоваться в скриптах и блоках.
| Наглядно увидеть, как описанный выше механизм разработки собственного пакета поддержки применяется на практике для работы с реальным оборудованием, можно в примере Сообщества: Разработка пакета поддержки оборудования для Engee.Интеграции. |
Вызов из блока Engee Function
В Common code начните с подключения Engee.Интеграции, как в стандартных .nglib блоках Оборудования, исходный код которых открыт.
package_dir = "/internal_persistent_vol/support_packages/locations/Engee-Device-Manager/EngeeDeviceManager.jl"
include("$(package_dir)/src/EngeeDeviceManager.jl")
using .EngeeDeviceManager
using .EngeeDeviceManager.Devices.MYDEVICE
Затем используйте обычный вызов методов:
mutable struct Block <: AbstractCausalComponent
dev::MYDEVICE.MyDevice
function Block()
d = MYDEVICE.MyDevice()
d.connect(host, Int(port))
new(d)
end
end
function (c::Block)(t::Real, channel)
return c.dev.read_value(Int(channel))
end
Здесь MYDEVICE — это имя модуля пользовательского пакета поддержки, соответствующее имени класса MyDevice в верхнем регистре.
Полный пример блока с параметрами, инициализацией и завершением
Типовой блок оборудования UDP RX использует в Common code три функции:
package_dir = "/internal_persistent_vol/support_packages/locations/Engee-Device-Manager/EngeeDeviceManager.jl"
include("$(package_dir)/src/EngeeDeviceManager.jl")
using .EngeeDeviceManager
using .EngeeDeviceManager.Devices.SOCKET
mutable struct Block <: AbstractCausalComponent
socket::SOCKET.Socket
function Block()
socket = SOCKET.Socket("AF_INET", "SOCK_DGRAM")
SOCKET.bind(socket, ip, port)
new(socket)
end
end
function (c::Block)(t::Real)
response = SOCKET.receive(c.socket, buf_size)
if response === nothing
return 0, zeros(UInt8, buf_size)
elseif length(response.data) == 0
return 0, zeros(UInt8, buf_size)
end
return length(response.data), resize!(response.data, buf_size)
end
function terminate!(c::Block)
SOCKET.close(c.socket)
return nothing
end
Разбор структуры:
-
Конструктор
Block()— вызывается один раз при запуске симуляции. В нем создается экземпляр устройства и вызываются методы инициализации (bind,open,connectи т.д.). Параметры блока (ip,port) берутся из маски и доступны как аргументы конструктора. -
Функция
(c::Block)(t::Real)— вызывается на каждом шаге симуляции. Это тело блока. Здесьt— текущее время, аc.поле— доступ к полям структуры. Возвращаемые значения соответствуют порядку выходных портов блока. -
Функция
terminate!(c::Block)— вызывается при завершении симуляции. Она освобождает ресурсы: закрывает сокеты, файлы, соединения. Ее вызов опционален.
Параметры блока задаются через маску (maskValues в .nglib). В Common code ссылайтесь на них по именам из маски: ip, port, buf_size, sample_time.
Пользовательский пакет поддержки подключается аналогично. В начале Common code обязательно укажите путь к модулю:
package_dir = "/internal_persistent_vol/support_packages/locations/Engee-Device-Manager/EngeeDeviceManager.jl"
include("$(package_dir)/src/EngeeDeviceManager.jl")
using .EngeeDeviceManager
using .EngeeDeviceManager.Devices.MYDEVICE
mutable struct Block <: AbstractCausalComponent
dev::MYDEVICE.MyDevice
function Block()
d = MYDEVICE.MyDevice()
d.connect(host, port)
new(d)
end
end
function (c::Block)(t::Real)
return c.dev.read_value(channel)
end
function terminate!(c::Block)
# закрытие соединения, если необходимо
return nothing
end
Частые проблемы
-
В блоке выводится сообщение
Method not found.Причина: метод приватный (
_…) или не имеет полных аннотаций типов. -
Пользовательский пакет поддержки не импортируется в Common code.
Причина: не выполнена синхронизация
syncExtensions(). Выполнитеengee.clear_all()в командной строке Engee. -
Изменили сигнатуры функций в модуле, но функции работают по-старому.
Причина: в Engee осталась старая версия пакета поддержки. Повторите
syncExtensions(). Если функции не обновились, выполнитеengee.clear_all()в командной строке Engee.