Engee documentation

Custom Engee support packages.Integrations

Subsystem Engee.Integrations provides interaction between Engee and external equipment through the client program and support packages.

You can develop your own custom support packages by extending the functionality of the Engee subsystem.Integration. The support package is a Python module that runs in the client program and returns the result back to Engee.

So that the Engee subsystem.Integration accepted custom packages, Engee automatically generates code for them in the Julia language. This allows you to call your functions directly from command line Engee img 41 1 2 or from blocks.

Technology is at the heart of this mechanism. RPC (Remote Procedure Call). You are calling a function in Julia that passes arguments over the network to the client computer. There, your support package executes the corresponding Python function with these parameters and returns the result back to Engee.

Execution Architecture

It is important to understand the distribution of code execution:

  • Python code is executed directly on the user’s computer during the client program process;

  • Julia code is executed in the subsystem Engee and coordinates the interaction.

For example, when calling:

using Main.EngeeDeviceManager.Devices.EXTDEVICE
device = EXTDEVICE.Extdevice()
device.function()

Julia-the code is executed in Engee, and Python is a function function() — on the client computer, and its result is returned to Engee.

Available Python Packages

When developing custom support packages, all standard Python packages are available to you, as well as the following third-party packages:

list of available Python packages
Package Description

aiohttp

Asynchronous HTTP client/server framework (asyncio)

autoflake

Deletes unused imports and variables

bandit

Static Python Code Security Analyzer

beartype

Fast hybrid type checking at runtime

black

An uncompromising code formatter

certifi

Mozilla’s CA Bundle Package

cffi

Interface of external functions for invoking C code from Python

docformatter

Formats documentation strings in accordance with PEP 257

flake8

Modular source Code Checker: pep8 pyflakes and others

gpib-ctypes

GPIB interface for Python, implemented using ctypes

hid

Ctypes bindings for hidapi

httpx

Next-generation HTTP client

intelhex

Python library for manipulating Intel HEX files

isort

Utility/library for sorting Python imports

jinja2

A very fast and expressive template engine

jupyter-client

Jupyter protocol implementation and client libraries

jupyter-core

Jupyter Basic Package

jwcrypto

Implementation of JOSE Web standards

mdurl

Markdown URL Utilities

msgpack

The MessagePack Serializer

multidict

Implementation of multidict

numpy

A fundamental package for computing arrays in Python

ordered-set

An OrderedSet that remembers its order

patchelf

A utility for changing dynamic linker and RPATH ELF executable files

pathspec

A utility for matching file paths in the gitignore style

platformdirs

Identifying suitable platform-specific directories

pydantic

Data validation using Python type hints

pydantic-core

The main functionality for Pydantic validation and serialization

pydantic-settings

Managing settings using Pydantic

pyduinocli

The wrapper around the arduino-cli

pyflakes

Passive Python Program Checker

pymodbus

Full-featured Modbus protocol stack in Python

pyserial

Python Serial Port Extension

pyusb

The USB access module from Python

pyvisa

Python VISA bindings for GPIB, RS232, TCPIP and USB tools

pyvisa-py

Pure Python implementation of the VISA library

pyyaml

YAML Parser and Emitter for Python

pyzmq

Python bindings for 0MQ

redis

Python client for the Redis database and key-value storage

requests

HTTP for people in Python

setuptools

Easily download, build, install, update, and remove Python packages.

toml

Python Library for Tom’s Obvious, Minimal Language

untokenize

Converts tokens to source code (while preserving spaces)

urllib3

HTTP library with a thread-safe connection pool, file sending, and other features

An example of creating a multi-file support package

For your convenience, we have prepared archive with the finished project structure and code examples. You will find the complete folder structure in the archive. devices and targets with working examples.

Let’s create a support package with our own file hierarchy. To do this, we use a template folder. extension, which has directories inside it devices and targets.

  • Device (Device) is an arbitrary custom class that does not interact with models. Engee and it doesn’t use their data.

  • Target is a custom class that interacts with the model Engee, receives data from it for processing and executes on another platform (microcontroller, separate computer, etc.).

In our example, we will create a device with a multi-file structure.

  1. Create in the folder devices a new folder extdevice.

  2. Inside extdevice create a file extdevice.py with the following code:

    import time
    from devices.base_device import BaseDevice
    from .models import DeviceConfig, CalculationResult
    
    class Extdevice(BaseDevice):
        def __init__(self, device_id: int, calibration_factor: float) -> None:
            self.device_id = device_id
            self.calibration_factor = calibration_factor
    
        def __del__(self) -> None:
            pass
    
        def complex_calculation(self, config: DeviceConfig) -> CalculationResult:
            # Сложные вычисления с использованием конфигурации
            result_value = (config.parameter_a * config.parameter_b +
                           config.parameter_c) * self.calibration_factor
    
            return CalculationResult(
                success=True,
                value=result_value,
                timestamp=time.time()
            )
    
        def get_status(self) -> str:
            return f"Device {self.device_id} operational with factor {self.calibration_factor}"
  3. Create a file models.py in the same folder extdevice:

    from devices.base_models import BaseModel
    
    class DeviceConfig(BaseModel):
        parameter_a: float
        parameter_b: int
        parameter_c: float
    
    class CalculationResult(BaseModel):
        success: bool
        value: float
        timestamp: float

For custom RPC classes, it is mandatory to inherit from BaseDevice (if it is a device) or from BaseTarget (if it’s a target).

Data structures must be inherited from BaseModel!

All methods must contain complete type annotations for the parameters and the return value. For example:

# НЕПРАВИЛЬНО - без аннотаций
def __init__(self, param1, param2):
    pass

# ПРАВИЛЬНО - с полными аннотациями
def __init__(self, param1: int, param2: float) -> None:
    pass

def calculate(self, x: float, y: int) -> str:
    return "result"

Without annotations, the system will not be able to correctly generate Julia code and convert data types between Python and Julia.

Methods intended to be called from a block should not start with and _ — such methods will not be available from scripts and blocks.

You can clearly see how the mechanism described above for creating your own support package is applied in practice to work with real hardware using the example of the Community.: Development of a hardware support package for Engee.Integration.

To debug the support package, you can use the built-in logging system. For more information, see Debugging support packages. To add information about the support package to the window «Equipment» implement a static method in the device class. __get_opportunity_list; for more information, see Displaying the support package status in the client program interface.

Uploading a custom package to Engee.Integrations

In the client program Engee.Integrations Follow these steps:

  1. Open the Hardware status window.

  2. Select the tab «Extensions».

    engee integrations custom packages 1 en

  3. Click «Add an extension».

  4. Select the folder with the custom support package that you want to add to the client program.

    engee integrations custom packages 2 en

If the download is successful, a message will appear in the diagnostic window of the client program.:

INFO: Расширение с именем extension было успешно загружено!

To get extended information about the found support package classes and methods in the diagnostic window, run the client program in developer mode.:

engee-device-manager.exe -d

After the support package has been successfully downloaded, additional information will be displayed in the diagnostic window.:

engee integrations custom packages 4 en

Registration and use of the support package in Engee

For a custom support package to appear in EngeeDeviceManager.Devices. or EngeeDeviceManager.Targets., run on the command line Engee:

using Main.EngeeDeviceManager
using Main.EngeeDeviceManager.UTILS_API

utils = UTILS_API.Utils()
UTILS_API.syncExtensions()

If you have changed method signatures or added new methods, call syncExtensions() again.

After registration, the support package becomes available for use.:

using Main.EngeeDeviceManager.Devices.EXTDEVICE
device = EXTDEVICE.Extdevice(123, 1.5)
Main.EngeeDeviceManager.Devices.EXTDEVICE.Extdevice("Extdevice", Base.UUID("0aac33b9-4ed5-4786-abb7-9855c0b80265"), ["complex_calculation", "get_status"], "Extdevice_0aac33b9-4ed5-4786-abb7-9855c0b80265_reply", Main.EngeeDeviceManager.Devices.EXTDEVICE.var"#complex_calculation#complex_calculation##0"{String, Base.UUID}("Extdevice", Base.UUID("0aac33b9-4ed5-4786-abb7-9855c0b80265")), Main.EngeeDeviceManager.Devices.EXTDEVICE.var"#get_status#get_status##0"{String, Base.UUID}("Extdevice", Base.UUID("0aac33b9-4ed5-4786-abb7-9855c0b80265")))
device.get_status()
"Device 123 operational with factor 1.5"
config = EXTDEVICE.DeviceConfig(2.5, 10, 3.14)
DeviceConfig(2.5, 10, 3.14)
result = device.complex_calculation(config)
CalculationResult(true, 42.21, 1.7851401913931458e9)

Your support package will be automatically downloaded every time you run the client program. If it is no longer needed, then you can delete it from the startup in the client program.:

engee integrations custom packages 3 en

Debugging support packages

Use the built-in logging system to debug the support package. Add to the code:

from main_logger import MainLogger

class Extdevice(BaseDevice):
    def __init__(self, device_id: int, calibration_factor: float) -> None:
        self.logger = MainLogger()
        self.logger.info(f"Initializing device {device_id}")
        self.device_id = device_id
        self.calibration_factor = calibration_factor

    def complex_calculation(self, config: DeviceConfig) -> CalculationResult:
        self.logger.debug("Starting complex calculation")
        # ... ваш код ...
        self.logger.info("Calculation completed successfully")
        return result

Available logging levels:

  • logger.debug("message") — debugging information;

  • logger.info ("message") — information messages;

  • logger.warning("message") — Warnings;

  • logger.error("message") — mistakes.

The messages will be displayed in the diagnostic window of the client program. You will also find examples of logging usage in the archive attached above.

Displaying the support package status in the client program interface

The client program displays installed hardware status: Whether the driver is loaded, whether the device is connected. To add information about the support package to the hardware status window, implement a static method in the device class. __get_opportunity_list.

How it works:

  1. At startup, the client program imports all modules from packages. devices and targets.

  2. A private method is checked for each registered class. __get_opportunity_list.

  3. If the method is found, it is called and returns a list. OpportunityStatus.

  4. The client program polls this list every 3 seconds and displays the information in the hardware status window.

Fields OpportunityStatus:

  • key — the unique identifier of the row. Example: "my_driver"

  • title_key — the title in the interface. Example: "MyDevice driver:"

  • value_key — the status text. Example: "Loaded" / "Not loaded"

  • value_args — arguments for formatting the value. Example: ()

  • color — indicator color: "green", "orange", "red". Example: "green"

  • category — a tab in the hardware status window:

    • "hardware" — «Equipment»

    • "protocols" — «Protocols»

    • "software" — «Software integration» (by default)

    • "embedded" — «Embedded systems»

If no field is specified, the category is determined automatically by the name of the device package.

An example for a custom support package:

from devices.base_device import BaseDevice

class MyDevice(BaseDevice):

    @staticmethod
    def __get_opportunity_list() -> list[BaseDevice.OpportunityStatus]:
        status = BaseDevice.opportunity_status

        driver_loaded = False
        try:
            import some_library  # замените на вашу зависимость
            driver_loaded = True
        except ImportError:
            pass

        results = []

        if driver_loaded:
            results.append(status(
                key="my_driver",
                title_key="MyDevice driver:",
                value_key="Loaded",
                color="green",
                category="hardware",
            ))
        else:
            results.append(status(
                key="my_driver",
                title_key="MyDevice driver:",
                value_key="Not loaded",
                color="orange",
                category="hardware",
            ))

        results.append(status(
            key="my_device",
            title_key="MyDevice status:",
            value_key="Available",
            color="green",
            category="hardware",
        ))

        return results

After adding this method, the support package will appear on the tab «Equipment» in the Engee client program.Integration.