Getting Started with Plugin Development

This page provides startup help for developing communication plugins for the use with the process integration mode of MERLIC. If you develop a communication plugin for the first time or if you are not sure how to start, you can use this guide to get information about the first steps and the general workflow for the plugin development.

The easiest way to develop a custom communication plugin is to use one of the provided example plugins as a basis and customize it as required to your use case. The following sections describe the general workflow for this including how to test the plugin during the development.

Using an Example Plugin as Basis

Customizing the Code of the Plugin

Testing the Plugin

Using an Example Plugin as Basis for a Custom Plugin

You can use one of our example communication plugins as basis for your plugin and customize the existing code as required. This way, you do not have to start from scratch and save a lot of time.

  1. Go to the example directory of the communication plugins. It is located in the subdirectory "examples\communication_plugins" within the MERLIC installation directory. For example:
    • Windows installation for all users: "%PROGRAMFILES%\MVTec\MERLIC-26.10\examples\communication_plugins"
    • Windows installation for the current user: "%LOCALAPPDATA%\Programs\MVTec\MERLIC-26.10\examples\communication_plugins"
    • Linux: "<INSTALLATION_DIR>/merlic_26.10.0/examples/communication_plugins"
  2. Copy the folder of example plugin you want to use as basis, for example, "event-logger".
  3. Rename the .cpp file of the example plugin, for example, "event-logger.cpp".
  4. Adjust the respective "CMakeList.txt" file:
    • File name of the .cpp file
    • Name of the target
    • (Description)
  5. Check if all includes required for your plugin are defined. For communication plugins, the following includes are mandatory:
    API version 2 definition and header includes.
    Current version of the Communicator Plugin API.
  6. Start to customize the .cpp file as described in the following sections.

Customizing the Code of the Plugin

API Functions of the Plugin

Check the implementation of the plugin functions in your plugin. Some of the functions are required for each communication plugin:

  • MVInfo
  • MVInit_V2
  • MVOpen
  • MVClose
  • MVStart
  • MVStop

As these functions are mandatory, you can already find an implementation of them in your copied plugin. You just need to customize the code for your plugin.

There are also some optional functions:

  • MVExpose
  • MVValidate

These are required if you want to define plugin parameters that can be configured via the graphical user interface of the MERLIC Runtime Environment Setup (MERLIC RTE Setup), and if you want to provide a proper validation of the parameter changes.

Adjusting the Functions for a Custom Plugin

Expand the following drop-down sections to get more detailed information about the function declarations and individual functions that are required for each communication plugin. The sections show the respective code excerpts based on the example plugin "event-logger" and describe which parts of the code need to be customized when developing your plugin. The drop-down sections for the optional functions also give more detailed information about their basic structure and for which use cases they should be implemented.

See also the page Life Cycle of a Plugin Instance for specific information about the life cycle of a plugin instance, especially regarding the implementation and use of the plugin API functions MVStart, MVStop, MVOpen, and MVClose. You can get information how to ensure that your plugin is valid and compatible for the use with MERLIC. It also contains information about the internal processes when starting MERLIC RTE and describes the steps that are performed when an instance of a plugin is used, for example, which functions are called for certain situations.

Required function declarations

Make sure to keep the following function declarations for the API functions symbols, which are expected to be present in the plugin shared library, in your .cpp file. They are required for each communication plugin.

extern "C" MVUserExport MVCode_t MVInfo(MVPluginInfo_t* pMVInfo);
extern "C" MVUserExport MVCode_t MVInit_V2(MVClass_V2_t* MVClass);
uint32_t MVCode_t
Type alias used for return codes.
Definition: mv_error_def.h:61
Data structure holding function pointers to plugin API functions.
Definition: mv_class_v2.h:38
Data structure which is populated by MVInfo with basic information about the plugin.
Definition: mv_plugin_info.h:41

If the plugin is written in a language different from C, these functions need to be given C linkage, for example, by marking them as extern "C" in the case of plugins written in C++. All further API functions do not need to have C linkage as the MVInit_V2 function is expected to set up pointers to the appropriate functions.

MVInfo and MVInit_V2
MVInfo

In MVInfo, you just have to set the capability of the plugin, that is, the value of the placeholder <CAPABILITY> in the code. You can choose between "monitor", "control" or both capabilities:

  • eMVCapabilities_Monitor
  • eMVCapabilities_Control
  • eMVCapabilities_Monitor | eMVCapabilities_Control

Plugins with the "monitor" capability can receive "events" that are provided by MERLIC whereas plugins with the "control" capability are allowed to send actions to MERLIC using either the MV_QueueAction() or MV_QueueAction_WithInfo() function. If you want to allow your plugin to receive "events" and send "actions", you have to set the capability to "eMVCapabilities_Monitor | eMVCapabilities_Control".

Define the capability of the plugin:

MVCode_t MVInfo(MVPluginInfo_t* pMVInfo)
{
pMVInfo->ApiMajor = MV_API_MAJOR;
pMVInfo->ApiMinor = MV_API_MINOR;
pMVInfo->ApiPatch = MV_API_PATCH;
pMVInfo->Flags = <CAPABILITY>;
return MV_CODE_OK;
}
#define MV_CODE_OK
The command was processed successfully.
Definition: mv_error_def.h:79
#define MV_API_PATCH
Patch API version; used to initialized MVPluginInfo_t::ApiPatch.
Definition: mv_version.h:27
#define MV_API_MAJOR
Major API version; used to initialized MVPluginInfo_t::ApiMajor.
Definition: mv_version.h:25
#define MV_API_MINOR
Minor API version; used to initialized MVPluginInfo_t::ApiMinor.
Definition: mv_version.h:26
uint32_t ApiMinor
Minor version of API used to compile the plugin; plugins should set this field to MV_API_MINOR.
Definition: mv_plugin_info.h:48
uint32_t ApiMajor
Major version of API used to compile the plugin; plugins should set this field to MV_API_MAJOR.
Definition: mv_plugin_info.h:44
uint32_t ApiPatch
Patch version of API used to compile the plugin; plugins should set this field to MV_API_PATCH.
Definition: mv_plugin_info.h:52
uint32_t Flags
Plugins should set this to a bitmask of the values in MVPluginCapabilities_t, reflecting the restrict...
Definition: mv_plugin_info.h:57
MVInit_V2

In MVInit_V2, you have to set the function pointers to the remaining plugin API functions to make them known to MERLIC RTE. In our example plugins, the mandatory plugin functions MVOpen, MVStart, MVStop, and MVClose are used. In addition, the function pointers to the optional functions MVExpose and MVValidate are set in MVInit_V2.

To customize the MVInit_V2 function for your plugin, it is sufficient to set up the pointers for the optional functions MVExpose and MVValidate depending on whether you want to implement the respective function or not. The function MVExpose is required if you want to define parameters for the plugin and to enable the configuration of your plugin parameters via the MERLIC RTE Setup. The function MVValidate is required if you also want to provide a proper validation of the parameter changes. Please see also the sections for MVExpose and MVValidate for more information.

Set up MVExpose and MVValidate functions:

MVCode_t MVInit_V2(MVClass_V2_t* MVClass)
{
MVClass->MVOpen = MVOpen;
MVClass->MVStart = MVStart;
MVClass->MVStop = MVStop;
MVClass->MVClose = MVClose;
MVClass->MVExpose = <nullptr|MVExpose>;
MVClass->MVValidate = <nullptr|MVValidate>;
return MV_CODE_OK;
}

It is possible to use arbitrary names for the functions. However, you have to make sure to set up the function pointer members of the MVClass_V2_t data structure accordingly as shown in the example code below:

Example: Set up the functions with arbitrary names

MVCode_t MVInit_V2(MVClass_V2_t* MVClass)
{
MVClass->MVOpen = OpenPlugin;
MVClass->MVStart = StartPlugin;
MVClass->MVStop = StopPlugin;
MVClass->MVClose = ClosePlugin;
MVClass->MVExpose = ExposeConfiguration;
MVClass->MVValidate = ValidateChanges;
return MV_CODE_OK;
}

The name of the MVInit_V2 function indicates the major version number of the API that is used for the plugin development. This mechanism allows a plugin to support multiple, otherwise incompatible, major versions of the API in the future by implementing MVInit_Vx for each supported major version and setting up the function pointers to either the same or different implementations as required.

MVOpen and MVClose
MVOpen

In MVOpen, a "user data" structure can be created which aggregates the collective state of one plugin instance. The use of such a user data structure is the recommended way to ensure that the plugin can manage some internal state which is then accessed and modified by each of the plugin API functions as well as any additional threads that the plugin might create. Typically, the data structure can be allocated in MVOpen and is then associated with the plugin handle via MV_Plugin_SetUserData() to make sure that it is available for the entire existence of the plugin instance. The pointer to the user data can then be retrieved from the plugin handle in each of the other plugin API functions via MV_Plugin_GetUserData(), for example, in MVClose to free up the memory again.

Using the user data mechanism ensures that the same plugin can be instantiated multiple times and that the internal state of the plugin instances does not interfere with one another as would be the case if globals or singletons were used instead. In case global state cannot be avoided due to requirements from third-party dependencies and a plugin cannot be instantiated safely multiple times, it is recommended to detect a repeated instantiation and return from MVOpen with a return code which is different from MV_CODE_OK.

If there are no restrictions that keep you from using this user data mechanism, you can customize the functions of the example plugin by adjusting the type of the user data structure as required. In our example plugins, the pointer to the user data is called *ud.

Adjust the user data struct:

static MVCode_t MVOpen(MVPlugin_t handle)
{
// Create the user data struct
<USER_DATA_STRUCT_TYPE>* ud = new <USER_DATA_STRUCT_TYPE>;
// ...and associate it with the plugin handle.
if (MV_Plugin_SetUserData(handle, static_cast<void*>(ud))
return MV_CODE_OK;
}
#define MV_CODE_INTERNAL_ERROR
A severe error occurred that caused the internal state of the vision system to become inconsistent.
Definition: mv_error_def.h:90
MVLibExport MVCode_t MV_Plugin_SetUserData(MVPlugin_t handle, void *pUserData)
Sets the plugin's user data.
struct _MVPlugin_t * MVPlugin_t
Opaque data type which represents a plugin instance handle.
Definition: mv_plugin_control_def.h:76
MVClose

In MVClose, the pointer to the user data is retrieved to free up the memory again. For this, you have to adjust the data type for the user data pointer as defined in MVOpen.

Adjust the user data struct:

static MVCode_t MVClose(MVPlugin_t handle)
{
// Retrieve the pointer to the user data from the plugin handle and
// free up the memory.
<USER_DATA_STRUCT_TYPE>* ud = nullptr;
if (MV_Plugin_GetUserData(handle, reinterpret_cast<void**>(&ud)) != MV_CODE_OK)
delete ud;
return MV_CODE_OK;
}
MVLibExport MVCode_t MV_Plugin_GetUserData(MVPlugin_t handle, void **pUserData)
Gets the plugin's user data.
MVStart and MVStop

MVStart and MVStop are invoked when a plugin instance is started and stopped, respectively. In between, the plugin is considered to be running and is allowed to queue actions (in case of "control" capability) and to receive events (in case of the "monitor" capability). The same plugin may be started and stopped multiple times and its configuration may be changed while it is not running. Hence, MVStart is typically the place where you can read the configuration options using MV_Plugin_GetConfig() and MV_PluginConfig_GetUserParameter().

MVStart

To access the internal state of the plugin instance, you can recall the pointer to the user data. In the code snippet below, it is called *ud. You can then use and modify the field of <USER_DATA_STRUCT_TYPE> in the custom implementation.

In addition, you can remove the code that defines how the example plugin reacts to "events" (in case, you are using the "event-logger" plugin as basis) and add the desired implementation of your custom plugin instead.

In the implementation of your plugin, the plugin should be able to process events as fast as MERLIC is sending them and must be able to handle dropped events. If a plugin is processing events slower than MERLIC is sending them, events are queued which may lead to delays. If the queue is full, additional events will be dropped. This may happen if MERLIC is running in continuous mode and the plugin takes longer to process events than MERLIC needs for a single iteration, or when a plugin is triggering single executions faster than another plugin is handling the results. Therefore, this issue should already be considered during the development of the plugin.

Adjust the user data struct and insert your code:

static MVCode_t MVStart(MVPlugin_t handle)
{
// Retrieve the pointer to the user data from the plugin handle.
<USER_DATA_STRUCT_TYPE>* ud = nullptr;
if (MV_Plugin_GetUserData(handle, reinterpret_cast<void**>(&ud)) != MV_CODE_OK)
// Start the event thread and store its handle in the user data struct.
ud->mEventThread = std::thread([handle] {
bool stop_event_thread = false;
while (!stop_event_thread)
{
MVEvent_t event;
MVCode_t status = MV_TryGetEvent(handle, MV_TIME_INFINITE, &event);
// Insert your implementation here
MV_Event_Clear(&event);
} // end event polling thread
});
return MV_CODE_OK;
}
MVLibExport void MV_Event_Clear(MVEvent_t *pEvent)
Destroys the event.
MVLibExport MVCode_t MV_TryGetEvent(MVPlugin_t handle, int32_t timeout, MVEvent_t *pEvent)
Attempts to retrieve a new event from this plugin's event queue.
#define MV_TIME_INFINITE
Infinite timeout: function blocks indefinitely until an event is received.
Definition: mv_event_def.h:121
struct _MVEvent_t * MVEvent_t
Opaque data type which holds all information related to a single event.
Definition: mv_event_def.h:144

For plugins with the capability eMVCapabilities_Monitor, that is, plugins that handle "events", it is quite typical to use the user data to store the handle to a thread which continuously polls for new events, that is, an "event loop". In order to be able to terminate such an event loop thread again, a special event of type eMVEvent_Shutdown will be enqueued when a plugin is stopped. This guarantees to be the last event that the plugin will receive before it is restarted and thus it is possible to break out of the event loop at that point.

In the "event-logger" example, this is done by setting "stop_event_thread" to "true":

Terminate the event loop in case of the Shutdown event:

{
MV_Log(handle, eMVSeverity_Info, format_msg("Event '%s' received; terminating event loop.", EventToString(event_type)));
stop_event_thread = true;
break;
}
@ eMVEvent_Shutdown
Shutdown
Definition: mv_event_def.h:87
MVLibExport void MV_Log(MVPlugin_t handle, MVSeverityLevel_t severity, const char *message)
Logs a message of given severity.
@ eMVSeverity_Info
Info severity.
Definition: mv_plugin_control_def.h:30
MVStop

In case an event loop thread has been started in MVStart, you have to join that thread again in MVStop. MVStop is called by MERLIC RTE at the same time the Shutdown event is enqueued, so there is no need to take any additional action to stop the thread. In case your plugin manages additional resources from third-party libraries, such as an I/O context or a database connection, it may be necessary to terminate those explicitly in MVStop.

Adjust the type of the user data struct:

static MVCode_t MVStop(MVPlugin_t handle)
{
// Retrieve the pointer to the user data from the plugin handle.
<USER_DATA_STRUCT_TYPE>* ud = nullptr;
if (MV_Plugin_GetUserData(handle, reinterpret_cast<void**>(&ud)) != MV_CODE_OK)
// Rejoin the event thread. It should be in the process of terminating after
// having received the shutdown event.
if (ud->mEventThread.joinable())
ud->mEventThread.join();
return MV_CODE_OK;
}
MVExpose and MVValidate
MVExpose

This function must be implemented if you want to define parameters for the plugin and enable the configuration of these parameters via the graphical user interface of the MERLIC RTE Setup.

You have to define the set of plugin parameters and their meta data ("user parameter description") for the configuration and use the family of MV_PluginUserParameterDescription_*() API functions to expose the user parameter description. If only predefined values should be allowed for the parameters, you also have to impose the required constraints via MV_PluginUserParameterDescription_ImposeConstraint().

This function is optional. However, if it is not implemented, the configuration of the plugin is not possible, neither via the MERLIC RTE Setup nor manually via JSON files because no parameters have been defined for the plugin.

Define the user parameter description:

{
// Insert your implementation here
return MV_CODE_OK;
}
struct _MVPluginUserParameterDescription_t * MVPluginUserParameterDescription_t
Opaque data type which stores the plugin-provided description of user configuration parameters.
Definition: mv_plugin_config_def.h:230
struct _MVPluginConfig_t * MVPluginConfig_t
Opaque data type which makes the plugin instance's configuration accessible.
Definition: mv_plugin_control_def.h:85
MVValidate

This function can be implemented if you want to provide a validation for more complex cases of parameter changes that are made in the MERLIC RTE Setup, for example, if the allowed value range of a parameter depends on other parameter values. MVValidate also enables you to provide feedback to the user about the state of the parameters, for example, to highlight invalid user parameter values or show specific error messages. This data structure is called "validation result".

For the implementation, you have to use the API functions MV_PluginConfig_* and MV_PluginConfigValidation_*.

Implement the validation of parameter changes and feedback of the parameter state:

{
// Insert your implementation here
return MV_CODE_OK;
}
struct _MVPluginConfigValidation_t * MVPluginConfigValidation_t
Opaque data type which stores the result of a configuration parameter validation.
Definition: mv_plugin_config_def.h:242

While adjusting the implementation for your communication plugin, you may want to test the plugin from time to time as described in the following sections.

Testing the Plugin

If you want to test your plugin during the implementation, you have to build the plugin and make sure that it is available for MERLIC RTE.

  1. Build the communication plugin. For detailed information on building plugins, see Building a Plugin.
  2. Make sure that the name of the plugin library begins with the prefix "pMV" prefix as described in Providing the Plugins for MERLIC RTE.
  3. Start the plugin to test the implementation. You have the following options to start the plugin:
    • Using the command line
    • Using the MERLIC RTE Setup

Testing the Plugin via Command Line

MERLIC provides the executable "merlic_communication_plugin_host.exe" which can be used for testing purposes during the plugin development. This executable starts the same process that is also started internally as a subprocess of MERLIC RTE when adding a communication plugin in the MERLIC RTE Setup. It serves as a host process for a specific instance of a communication plugin. Each plugin instance that is added in MERLIC RTE Setup, is executed in its own host process.

The manual use of this executable is only intended for testing purposes during the development of communication plugins to enable a quick feedback loop when iterating on the code of the plugin. The executable file is not intended to be started manually in productive use.

You can find the "merlic_communication_plugin_host.exe" executable next to the other MERLIC executable files in the MERLIC installation directory. On Windows system, they are installed by default in the subdirectoy "bin\x64-win64". On Linux systems, the executable files are installed in the subdirectory "x64-linux" or "aarch64-linux", respectively.

To start the host process for a plugin, you have to specify the following command line options: "--rte_instance" or "--i", "--plugin" or "-p", and "--plugin_dir":

merlic_communication_plugin_host.exe --rte_instance <NAME> --plugin <PLUGIN_NAME> --plugin_dir <PLUGIN_BUILD_OUTPUT_FOLDER>

Make sure to specify the directory in which the current build output of your custom plugin is located. When starting the host process for a plugin, the current configuration of the plugin will be written to the standard output.

Additionally, you have to specify the name of the MERLIC RTE instance the host process should connect to. Specifying the instance name is required because there is no "default" MERLIC RTE instance that can be assumed when developing communication plugins. If no instance name is specified, an error occurs. By default, a UUID (universally unique identifier) string is randomly generated when starting a MERLIC RTE instance. For easier usage and testing, we recommended to specify a simple, descriptive instance name when starting MERLIC RTE ("merlic_rte.exe --name <INSTANCE_NAME>"). This name can then be used when starting the host process for the plugin during the development process.

Example

The following Windows command line starts a host process, which connects to the MERLIC RTE instance with name "test-instance" and attempts to load the plugin library at "C:\Users\Public\Documents\MVTec\MERLIC\Custom_plugins\pMVmy_custom_plugin.dll":

merlic_communication_plugin_host.exe --rte_instance test-instance --plugin my_custom_plugin --plugin_dir C:\Users\Public\Documents\MVTec\MERLIC\Custom_plugins
Restrictions
When using the "merlic_communication_plugin_host.exe" executable to test the plugin, the following information and restrictions should be considered:
  • The executable only starts executing the specified communication plugin. MERLIC RTE is not started. If you want to test any commands that require the communication with the vision system, you have to start a MERLIC RTE process or test via the MERLIC RTE Setup in the usual way.
  • It is not possible to stop and restart the plugin.
Available Command Line Options

For "merlic_communication_plugin_host.exe", the following command line options are available:

Command line option Description
-h, --help Get help for the available command line options and arguments.
-V, --version Display the MERLIC version.
-l, --available_plugins Display the list of available plugins. MERLIC searches for dynamic libraries with files prefix "pMV" within the specified plugin directories.
-i, --rte_instance <NAME> Defines the name of the MERLIC RTE instance to which the plugin host should connect.
-p, --plugin <NAME> Name of the plugin to be registered. The corresponding plugin library will be loaded and the plugin will be started using its default configuration.
--plugin-dir <DIRECTORY> Defines the directory where to search for the plugin library. By default, the directory containing the executable is searched.
-d, --log-level <LOG_LEVEL> The log level for the plugin. The default log level is "debug". The available options are "debug", "info", "warning", "error", and "critical".
--command_timeout <MILLISECONDS> Set the duration in milliseconds after which a pending command to the vision system will time out. The default is 5 seconds. A negative value indicates that pending commands should block indefinitely.
-o, --override <KEY>=<VALUE> Override the default value of a configuration option. <KEY> must correspond to one of the plugin's user parameters. <VALUE> is parsed as the corresponding data type. This option can be specified multiple times to override multiple parameters.

Testing the Plugin in MERLIC RTE Setup

If you want to test the communication between your plugin and MERLIC RTE, or if you implemented the MVExpose and MVValidate functions for your plugin and want to test the plugin configuration and validation, you can test the plugin in the MERLIC RTE Setup. In this case, you have to proceed in the same way as a MERLIC user with the final communication plugin. This also implies that you have to ensure the plugin is available for MERLIC RTE as described in Providing the Plugins for MERLIC RTE:

  • The plugin library must be located in the specified plugin directory.
  • The name of the plugin library must begin with the prefix "pMV".

If these prerequisites are fulfilled, you can start testing in the MERLIC RTE Setup:

  1. Open the MERLIC RTE Setup.
  2. Start MERLIC RTE.
  3. Add an instance of your communication plugin in the "Communication" tab.
  4. Adjust and test the configuration of the plugin.
  5. Start and stop the plugin as required for testing.

For more information on working with communication plugins in the MERLIC RTE Setup, see the MERLIC Manual.

When you are finished implementing your communication plugin, make sure to provide the plugin library as described in Providing the Plugins for MERLIC RTE.

Logging

The logging information for communication plugins is written to the log file of MERLIC RTE. By default, the log files are written to the directory "%LOCALAPPDATA%\MVTec\MERLIC\" (on Windows systems) and "~/.local/share/MVTec/MERLIC/" (on Linux systems). The log file for MERLIC RTE begins with the prefix "merlic_rte_".

When adding a plugin in the MERLIC RTE Setup, the log level of the plugin is set to the log level of the respective MERLIC RTE process by default. In some cases, the log levels might differ, for example, if MERLIC RTE is stopped and restarted with a different log level. In this case, the log level of the plugin instance that was added in the previous session is still set to the same log level as before.

If the log level of a plugin instance differs from the log level of MERLIC RTE, the plugin instance will only be able to use its log level in the following case: the log level of MERLIC RTE is lower or the same as the log level of the plugin instance.

This behavior should be considered for use cases in which the log levels might differ. For example, if the plugin was added with "debug" log level while MERLIC RTE uses the higher log level "info". In this case, only messages of log level "info" will be logged for the plugin instance.