Plugins

Covers Engine Plugin SDK 2.0.0 · Interface Plugin SDK 0.2.0

Engine Plugin

Create a native plugin, register behavior, and build it for your engine.

An engine plugin is a native library loaded by DARTWIC. It registers reusable behavior when loaded and can create module instances from project configuration. Start with the example project; this guide uses its existing class and build setup.

1. Set the plugin identity

Edit the root plugin.json. Keep contains_engine_plugin: true, and set contains_interface_plugin to whether you ship UI. Set minEngineVersion to the oldest engine you actually support. Keep the plugin ID consistent in the manifest, interface definition, and generated output folders. The manifest table explains every required field.

2. Register one operation

In engine/src/example_device_plugin.cpp, the starter’s onPluginLoaded() registers features through the provided dartwic SDK pointer. This operation returns the caller’s payload:

void ExampleDevicePlugin::onPluginLoaded() {
    dartwic->registerOperation(
        "echo",
        "Echo",
        [](const nlohmann::json& payload) {
            return nlohmann::json{{"echo", payload}};
        }
    );
}

Local registration IDs are prefixed with your plugin ID. With the unmodified starter, the operation is example_device_plugin.echo. It can be called through the Python client or another TEMPEST client:

response = client.operation("example_device_plugin.echo", {"message": "hello"})
response.raise_for_error()
print(response.payload["echo"])

The exported entry point creates the plugin object. Keep the starter’s export macro and signature:

DARTWIC_PLUGIN_EXPORT DARTWIC::Plugins::BasePlugin* createPlugin(
    nlohmann::json cfg,
    DARTWIC::API::SDK_API* api
) {
    return new Example::ExampleDevicePlugin(cfg, api);
}

3. Add a module for a device

Register a module type in onPluginLoaded(), then return an instance from createModule:

dartwic->registerModuleType({
    .id = "example_device",
    .name = "Example Device"
});
DARTWIC::Modules::BaseModule* ExampleDevicePlugin::createModule(
    const std::string& module_type_id,
    nlohmann::json cfg,
    DARTWIC::API::SDK_API* api
) {
    if (module_type_id != "example_device") return nullptr;
    return new ExampleDeviceModule(cfg, api);
}

The factory receives the local type ID. Each module instance receives merged configuration and its SDK pointer. Own the device connection, retry state, and cleanup in the module. Put scheduled reads in a registered loop or task; avoid blocking acquisition or control with unrelated network work.

Modules explains instance configuration. The example also demonstrates task types, DCode functions, a loop, and a custom Share transport.

4. Publish channels and respect authority

Using the SDK pointer inside your implementation:

using DARTWIC::API::ChannelField;
using DARTWIC::API::RecordMode;
api->upsertChannelField("demo_temperature", ChannelField::VALUE, 23.5);
api->upsertChannelField("demo_temperature", ChannelField::UNITS, std::string("degC"));
api->upsertChannelField("demo_temperature", ChannelField::STALE_TIMEOUT, 2.0);
api->upsertChannelField("demo_temperature", ChannelField::RECORD_MODE, RecordMode::OnValueChange);

For automation-owned outputs, use claimChannel or commandChannel; for owner-updated diagnostics use setChannel. Plain value updates do not claim a free channel. Read Commanding and Control before exposing outputs.

A value commit can run dependent DCode calculations and event reactions inline. Telemetry delivery and recording run asynchronously. Your driver determines whether an accepted output is sent immediately, queued, or picked up on its next poll. Document that behavior and expose feedback or failure diagnostics; an accepted channel write alone does not prove a device acted.

5. Build and load

For a driver feeding native DCode, declare fixed channels during configuration and reuse resolved batch handles in acquisition callbacks. DARTWIC Builder reports layout changes and rebinding of loaded consumers.

Call TaskRuntime::setFixedInputChannels from on_configure. Every declared name must refer to fixed storage when configuration completes; a missing or dynamic input now fails task preparation with a named error. An empty declaration remains valid for a task with no fixed inputs. Calls from other callbacks throw, so changing a running task’s input plan requires reconfiguration.

Install a supported C++ toolchain, CMake, and the vcpkg dependencies listed in the starter. Set VCPKG_ROOT to your vcpkg checkout, then run from the plugin root:

npm run package

For a debug engine, use npm run package-debug. Match operating system, architecture, SDK, and debug/release runtime. Follow Packaging and Installation to install the output, restart the host, and check the loaded plugin before creating module instances.

For a complete acquisition, DCode automation, and device-write example, see Building a hardware driver.

SDK map

CapabilityReference
Plugin entry and lifecycleBasePlugin
Module instancesBaseModule
Channel updatesChannels
Control ownershipChannel Authority
Tasks and loopsEngine SDK
Native script functionsDCode
Request handlersOperations
Register a Share transportShare registration
Custom frame delivery and protocol APIsShare SDK

The DARTWIC Share guide shows how to pair the example engine transport with an external C++ application using the Protocol SDK.

If a library fails to load, check the engine log for missing dependencies or entry points. If the plugin loads but a module does not, check the instance’s plugin, module_type, and parameter values.