Skip to content

Plugin Development Guide

Complete guide to developing instrument driver plugins for the Instrument Server.

Table of Contents

Overview

Plugins are shared libraries (. so on Linux, .dll on Windows, .dylib on macOS) that implement the instrument driver interface. The server loads plugins dynamically at runtime.

Why Plugins?

  • Modularity: Add new instruments without recompiling server
  • Isolation: Plugin crashes don't affect server
  • Flexibility: Use any SDK (VISA, vendor SDKs, custom protocols)
  • Distribution: Distribute plugins independently
  • Easy Development: Use add_instrument_plugin() CMake helper

Plugin Types

Type Description Examples
Protocol adapter Generic protocol handler VISA, Serial, TCP
Vendor SDK Wraps vendor library NI-DAQmx, Keithley SDK
Custom protocol Proprietary communication Custom serial, USB HID

Built-in Plugins

The server includes the following built-in plugins that are automatically loaded:

Protocol Description Status
VISA NI-VISA protocol for GPIB, USB, Ethernet, Serial ✅ Built-in

These plugins are available without any configuration. You only need to create custom plugins for:

  • Proprietary vendor SDKs (NI-DAQmx, Keithley, etc.)
  • Custom protocols not covered by VISA
  • Special communication requirements

Plugin Interface

Required Functions

Every plugin must export these four C functions:

PluginMetadata plugin_get_metadata(void);
int32_t plugin_initialize(const PluginConfig *config);
int32_t plugin_execute_command(const PluginCommand *cmd, PluginResponse *resp);
void plugin_shutdown(void);

Header File

Location: <instrument-server/plugin/PluginInterface.h>

After installing InstrumentServer, this header is available at:

  • Linux/macOS: /usr/local/include/instrument-server/plugin/PluginInterface.h
  • Windows: C:\Program Files\InstrumentServer\include\instrument-server\plugin\PluginInterface.h
#ifndef INSTRUMENT_PLUGIN_INTERFACE_H
#define INSTRUMENT_PLUGIN_INTERFACE_H

#include <stdint.h>

#define INSTRUMENT_PLUGIN_API_VERSION 1
#define PLUGIN_MAX_STRING_LEN 256
#define PLUGIN_MAX_PAYLOAD 4096
#define PLUGIN_MAX_PARAMS 32

// Parameter types
typedef enum {
    PARAM_TYPE_NONE = 0,
    PARAM_TYPE_DOUBLE = 1,
    PARAM_TYPE_INT64 = 2,
    PARAM_TYPE_STRING = 3,
    PARAM_TYPE_BOOL = 4,
    PARAM_TYPE_UINT64 = 5
} ParamType;

// Parameter value union
typedef struct {
    ParamType type;
    union {
        double d_val;
        int64_t i64_val;
        uint64_t u64_val;
        char str_val[PLUGIN_MAX_STRING_LEN];
        bool b_val;
    } value;
} PluginParamValue;

// Named parameter
typedef struct {
    char name[PLUGIN_MAX_STRING_LEN];
    PluginParamValue value;
} PluginParam;

// Plugin metadata
typedef struct {
    uint32_t api_version;
    char name[PLUGIN_MAX_STRING_LEN];
    char version[PLUGIN_MAX_STRING_LEN];
    char protocol_type[PLUGIN_MAX_STRING_LEN];
    char description[PLUGIN_MAX_STRING_LEN];
} PluginMetadata;

// Plugin configuration
typedef struct {
    char instrument_name[PLUGIN_MAX_STRING_LEN];
    char connection_json[PLUGIN_MAX_PAYLOAD];  // JSON string
} PluginConfig;

// Command from server
typedef struct {
    char id[PLUGIN_MAX_STRING_LEN];
    char instrument_name[PLUGIN_MAX_STRING_LEN];
    char verb[PLUGIN_MAX_STRING_LEN];
    bool expects_response;

    uint32_t param_count;
    PluginParam params[PLUGIN_MAX_PARAMS];
} PluginCommand;

// Response to server
typedef struct {
    char command_id[PLUGIN_MAX_STRING_LEN];
    char instrument_name[PLUGIN_MAX_STRING_LEN];

    bool success;
    int32_t error_code;
    char error_message[PLUGIN_MAX_STRING_LEN];

    char text_response[PLUGIN_MAX_PAYLOAD];
    PluginParamValue return_value;
} PluginResponse;

// Plugin API functions (must be exported)
#ifdef __cplusplus
extern "C" {
#endif

PluginMetadata plugin_get_metadata(void);
int32_t plugin_initialize(const PluginConfig *config);
int32_t plugin_execute_command(const PluginCommand *cmd, PluginResponse *resp);
void plugin_shutdown(void);

#ifdef __cplusplus
}
#endif

#endif // INSTRUMENT_PLUGIN_INTERFACE_H

Quick Start

This is the recommended workflow for creating a custom plugin after installing InstrumentServer.

Step 1: Install InstrumentServer

# Clone and build InstrumentServer
git clone https://github.com/falcon-autotuning/instrument-script-server.git
cd instrument-script-server
mkdir build && cd build
cmake .. 
cmake --build .
sudo cmake --install .

This installs:

  • Headers to /usr/local/include/instrument-server/
  • CMake config to /usr/local/lib/cmake/InstrumentServer/
  • Helper function add_instrument_plugin()

Step 2: Create Your Plugin Project

mkdir my_instrument_plugin
cd my_instrument_plugin

Create CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(MyInstrumentPlugin VERSION 1.0.0)

# Find the installed InstrumentServer package
find_package(InstrumentServer REQUIRED)

# Create your plugin using the helper function
add_instrument_plugin(my_instrument_plugin
  SOURCES 
    my_plugin.c
  LINK_LIBRARIES
    # Add your instrument's SDK here
    # my_instrument_sdk
)

# Install to standard location
install(TARGETS my_instrument_plugin
  LIBRARY DESTINATION lib/instrument-plugins
)

Create my_plugin.c:

#include <instrument-server/plugin/PluginInterface.h>
#include <string.h>

// Your instrument SDK includes
// #include <my_instrument_sdk.h>

static void *g_device_handle = NULL;

PluginMetadata plugin_get_metadata(void) {
  PluginMetadata meta = {0};
  meta.api_version = INSTRUMENT_PLUGIN_API_VERSION;
  strncpy(meta.name, "My Instrument Plugin", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta. version, "1.0.0", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.protocol_type, "MyInstrument", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.description, "Driver for my custom instrument", PLUGIN_MAX_STRING_LEN - 1);
  return meta;
}

int32_t plugin_initialize(const PluginConfig *config) {
  // Parse config->connection_json to get connection parameters
  // Open connection to instrument
  // g_device_handle = my_sdk_open(... );

  if (g_device_handle == NULL) {
    return -1;
  }

  return 0;
}

int32_t plugin_execute_command(const PluginCommand *cmd, PluginResponse *resp) {
  strncpy(resp->command_id, cmd->id, PLUGIN_MAX_STRING_LEN - 1);
  strncpy(resp->instrument_name, cmd->instrument_name, PLUGIN_MAX_STRING_LEN - 1);

  if (strcmp(cmd->verb, "IDN") == 0) {
    // Query instrument identity
    // char idn[256];
    // my_sdk_query(g_device_handle, "*IDN?", idn);

    resp->success = true;
    strncpy(resp->text_response, "My Instrument v1.0", PLUGIN_MAX_PAYLOAD - 1);
    return 0;
  }

  if (strcmp(cmd->verb, "MEASURE") == 0) {
    // Perform measurement
    // double value = my_sdk_measure(g_device_handle);

    resp->success = true;
    resp->return_value.type = PARAM_TYPE_DOUBLE;
    resp->return_value.value.d_val = 3.14159;  // Replace with actual measurement
    return 0;
  }

  resp->success = false;
  strncpy(resp->error_message, "Unknown command", PLUGIN_MAX_STRING_LEN - 1);
  return -1;
}

void plugin_shutdown(void) {
  if (g_device_handle) {
    // my_sdk_close(g_device_handle);
    g_device_handle = NULL;
  }
}

Step 3: Build and Install

mkdir build && cd build
cmake .. 
cmake --build .
sudo cmake --install .

Your plugin is now installed to /usr/local/lib/instrument-plugins/my_instrument_plugin.so

Step 4: Test Your Plugin

Create instrument configuration:

# my_instrument. yaml
name: MyInstrument1
api_ref: my_api. yaml
connection: 
  type: MyInstrument  # Must match plugin's protocol_type
  address: "COM3"      # Or whatever your instrument needs
# my_api.yaml
protocol: 
  type: MyInstrument

commands:
  IDN:
    description: "Identity query"
    template: "IDN"
    response_type: string

  MEASURE:
    description:  "Perform measurement"
    template: "MEASURE"
    response_type: double

Test the plugin:

# Test with explicit plugin path
instrument-server test my_instrument.yaml IDN --plugin ./build/my_instrument_plugin.so

# After installation, test without path
instrument-server test my_instrument.yaml IDN

# If successful, start using it
instrument-server daemon start
instrument-server start my_instrument.yaml
instrument-server status MyInstrument1

Development Workflow

Using add_instrument_plugin() Helper

The add_instrument_plugin() CMake function (provided by InstrumentServer) automatically handles:

✅ Creating MODULE library (for dynamic loading)
✅ Removing lib prefix
✅ Setting correct suffix (. so/. dll/. dylib)
✅ Position-independent code
✅ Symbol visibility
✅ Include paths

Basic usage:

find_package(InstrumentServer REQUIRED)

add_instrument_plugin(my_plugin
  SOURCES my_plugin. c
)

With SDK dependencies:

add_instrument_plugin(my_plugin
  SOURCES 
    my_plugin.c
    helper. c
  LINK_LIBRARIES
    my_vendor_sdk
  INCLUDE_DIRS
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

Multiple plugins:

add_instrument_plugin(plugin1 SOURCES plugin1.c)
add_instrument_plugin(plugin2 SOURCES plugin2.c)
add_instrument_plugin(plugin3 SOURCES plugin3.c)

Without add_instrument_plugin() Helper

If you prefer manual control or can't use the helper:

cmake_minimum_required(VERSION 3.20)
project(MyPlugin)

add_library(my_plugin MODULE my_plugin.c)

set_target_properties(my_plugin PROPERTIES
  PREFIX ""                          # No 'lib' prefix
  POSITION_INDEPENDENT_CODE ON
  C_VISIBILITY_PRESET default        # Export symbols
)

target_include_directories(my_plugin PRIVATE
  /usr/local/include  # Or wherever InstrumentServer is installed
)

# Link your SDK
target_link_libraries(my_plugin PRIVATE my_instrument_sdk)

install(TARGETS my_plugin LIBRARY DESTINATION lib/instrument-plugins)

Building Plugins

Linux

With CMake (recommended):

mkdir build && cd build
cmake .. 
cmake --build .
sudo cmake --install .

Manual compilation (for simple plugins):

gcc -shared -fPIC -o my_plugin.so my_plugin.c \
    -I/usr/local/include \
    -L/usr/local/lib -lmy_sdk

macOS

With CMake:

mkdir build && cd build
cmake ..
cmake --build .
sudo cmake --install .

Manual compilation:

clang -shared -fPIC -o my_plugin.dylib my_plugin. c \
      -I/usr/local/include \
      -L/usr/local/lib -lmy_sdk

Windows

With CMake and MSVC:

mkdir build
cd build
cmake .. -G "Visual Studio 16 2019"
cmake --build .  --config Release
cmake --install . --config Release

With MinGW:

gcc -shared -o my_plugin. dll my_plugin.c ^
    -I"C:\Program Files\InstrumentServer\include" ^
    -L"C:\Program Files\MySDK\lib" -lmy_sdk

Cross-Platform CMake

cmake_minimum_required(VERSION 3.20)
project(MyPlugin)

find_package(InstrumentServer REQUIRED)

# Works on all platforms
add_instrument_plugin(my_plugin
  SOURCES my_plugin.c
  LINK_LIBRARIES my_sdk
)

# Platform-specific options
if(WIN32)
  target_compile_definitions(my_plugin PRIVATE WINDOWS_SPECIFIC)
elseif(APPLE)
  target_compile_definitions(my_plugin PRIVATE MACOS_SPECIFIC)
else()
  target_compile_definitions(my_plugin PRIVATE LINUX_SPECIFIC)
endif()

install(TARGETS my_plugin LIBRARY DESTINATION lib/instrument-plugins)

Testing Plugins

1. Verify Plugin Loads

# Check if plugin is discoverable
instrument-server plugins

# Should list your plugin if installed to standard location

2. Test Metadata

# Test with explicit path
instrument-server discover /path/to/my/plugins

# Should show plugin details

3. Test Commands

# Create minimal config files (shown in Quick Start)
# Then test individual commands

instrument-server test my_instrument.yaml IDN
instrument-server test my_instrument.yaml MEASURE

4. Integration Testing

# Start daemon
instrument-server daemon start

# Start instrument
instrument-server start my_instrument. yaml

# Check status
instrument-server status MyInstrument1

# Run measurement script
instrument-server measure dc test_measurement.lua

# Cleanup
instrument-server stop MyInstrument1
instrument-server daemon stop

5. Unit Testing Plugin Directly

Create a test program:

// test_plugin.c
#include <assert.h>
#include <stdio.h>
#include <dlfcn.h>
#include <instrument-server/plugin/PluginInterface.h>

int main() {
  // Load plugin
  void *handle = dlopen("./my_plugin.so", RTLD_NOW);
  assert(handle != NULL);

  // Get metadata
  typedef PluginMetadata (*GetMetadataFunc)(void);
  GetMetadataFunc get_metadata = dlsym(handle, "plugin_get_metadata");
  assert(get_metadata != NULL);

  PluginMetadata meta = get_metadata();
  printf("Plugin:  %s v%s\n", meta.name, meta.version);
  printf("Protocol: %s\n", meta.protocol_type);
  assert(meta.api_version == INSTRUMENT_PLUGIN_API_VERSION);

  // Test initialize
  typedef int32_t (*InitFunc)(const PluginConfig*);
  InitFunc init = dlsym(handle, "plugin_initialize");
  assert(init != NULL);

  PluginConfig config = {0};
  strncpy(config.instrument_name, "Test", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(config.connection_json, "{\"address\":\"test\"}", PLUGIN_MAX_PAYLOAD - 1);

  int result = init(&config);
  printf("Initialize result: %d\n", result);
  assert(result == 0);

  // Test command
  typedef int32_t (*ExecFunc)(const PluginCommand*, PluginResponse*);
  ExecFunc exec = dlsym(handle, "plugin_execute_command");
  assert(exec != NULL);

  PluginCommand cmd = {0};
  strncpy(cmd.id, "test-1", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(cmd.instrument_name, "Test", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(cmd.verb, "IDN", PLUGIN_MAX_STRING_LEN - 1);
  cmd.expects_response = true;

  PluginResponse resp = {0};
  result = exec(&cmd, &resp);
  printf("Command result: %d, success: %d\n", result, resp.success);
  printf("Response: %s\n", resp.text_response);

  // Cleanup
  typedef void (*ShutdownFunc)(void);
  ShutdownFunc shutdown = dlsym(handle, "plugin_shutdown");
  if (shutdown) {
    shutdown();
  }

  dlclose(handle);
  printf("All tests passed!\n");
  return 0;
}

Compile and run:

gcc -o test_plugin test_plugin.c -ldl -I/usr/local/include
./test_plugin

Example Plugins

Example 1: Simple Echo Plugin (No Dependencies)

#include <instrument-server/plugin/PluginInterface.h>
#include <string.h>
#include <stdio.h>

static int g_initialized = 0;

PluginMetadata plugin_get_metadata(void) {
  PluginMetadata meta = {0};
  meta.api_version = INSTRUMENT_PLUGIN_API_VERSION;
  strncpy(meta.name, "Echo Plugin", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.version, "1.0.0", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.protocol_type, "Echo", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.description, "Simple echo plugin for testing", PLUGIN_MAX_STRING_LEN - 1);
  return meta;
}

int32_t plugin_initialize(const PluginConfig *config) {
  fprintf(stderr, "[Echo] Initializing for %s\n", config->instrument_name);
  g_initialized = 1;
  return 0;
}

int32_t plugin_execute_command(const PluginCommand *cmd, PluginResponse *resp) {
  strncpy(resp->command_id, cmd->id, PLUGIN_MAX_STRING_LEN - 1);
  strncpy(resp->instrument_name, cmd->instrument_name, PLUGIN_MAX_STRING_LEN - 1);

  if (!g_initialized) {
    resp->success = false;
    strncpy(resp->error_message, "Not initialized", PLUGIN_MAX_STRING_LEN - 1);
    return -1;
  }

  // Echo back the command verb
  resp->success = true;
  snprintf(resp->text_response, PLUGIN_MAX_PAYLOAD, "Echo: %s", cmd->verb);
  return 0;
}

void plugin_shutdown(void) {
  fprintf(stderr, "[Echo] Shutting down\n");
  g_initialized = 0;
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(EchoPlugin)

find_package(InstrumentServer REQUIRED)
add_instrument_plugin(echo_plugin SOURCES echo_plugin.c)
install(TARGETS echo_plugin LIBRARY DESTINATION lib/instrument-plugins)

Example 2: Serial Port Plugin

#include <instrument-server/plugin/PluginInterface.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>

static int g_fd = -1;

PluginMetadata plugin_get_metadata(void) {
  PluginMetadata meta = {0};
  meta.api_version = INSTRUMENT_PLUGIN_API_VERSION;
  strncpy(meta.name, "Serial Port Plugin", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta. version, "1.0.0", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.protocol_type, "Serial", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.description, "Serial port communication", PLUGIN_MAX_STRING_LEN - 1);
  return meta;
}

int32_t plugin_initialize(const PluginConfig *config) {
  // Parse port from config->connection_json
  const char *port = "/dev/ttyUSB0";  // TODO: Parse from JSON

  g_fd = open(port, O_RDWR | O_NOCTTY);
  if (g_fd < 0) {
    return -1;
  }

  // Configure serial port
  struct termios options;
  tcgetattr(g_fd, &options);
  cfsetispeed(&options, B9600);
  cfsetospeed(&options, B9600);
  options.c_cflag |= (CLOCAL | CREAD);
  options.c_cflag &= ~PARENB;
  options.c_cflag &= ~CSTOPB;
  options.c_cflag &= ~CSIZE;
  options.c_cflag |= CS8;
  tcsetattr(g_fd, TCSANOW, &options);

  return 0;
}

int32_t plugin_execute_command(const PluginCommand *cmd, PluginResponse *resp) {
  strncpy(resp->command_id, cmd->id, PLUGIN_MAX_STRING_LEN - 1);
  strncpy(resp->instrument_name, cmd->instrument_name, PLUGIN_MAX_STRING_LEN - 1);

  if (g_fd < 0) {
    resp->success = false;
    strncpy(resp->error_message, "Port not open", PLUGIN_MAX_STRING_LEN - 1);
    return -1;
  }

  // Send command
  char command[256];
  snprintf(command, sizeof(command), "%s\n", cmd->verb);
  write(g_fd, command, strlen(command));

  // Read response
  char buffer[1024];
  int n = read(g_fd, buffer, sizeof(buffer) - 1);

  if (n > 0) {
    buffer[n] = '\0';
    resp->success = true;
    strncpy(resp->text_response, buffer, PLUGIN_MAX_PAYLOAD - 1);
    return 0;
  }

  resp->success = false;
  strncpy(resp->error_message, "Read failed", PLUGIN_MAX_STRING_LEN - 1);
  return -1;
}

void plugin_shutdown(void) {
  if (g_fd >= 0) {
    close(g_fd);
    g_fd = -1;
  }
}

Example 3: VISA Plugin

#include <instrument-server/plugin/PluginInterface.h>
#include <visa. h>
#include <string. h>

static ViSession g_rm = VI_NULL;
static ViSession g_instr = VI_NULL;

PluginMetadata plugin_get_metadata(void) {
  PluginMetadata meta = {0};
  meta.api_version = INSTRUMENT_PLUGIN_API_VERSION;
  strncpy(meta.name, "VISA Plugin", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.version, "1.0.0", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.protocol_type, "VISA", PLUGIN_MAX_STRING_LEN - 1);
  strncpy(meta.description, "VISA/SCPI instrument driver", PLUGIN_MAX_STRING_LEN - 1);
  return meta;
}

int32_t plugin_initialize(const PluginConfig *config) {
  // Parse VISA address from config->connection_json
  const char *address = "TCPIP:: 192.168.1.1:: INSTR";  // TODO: Parse from JSON

  ViStatus status = viOpenDefaultRM(&g_rm);
  if (status < VI_SUCCESS) {
    return -1;
  }

  status = viOpen(g_rm, address, VI_NULL, VI_NULL, &g_instr);
  if (status < VI_SUCCESS) {
    viClose(g_rm);
    return -2;
  }

  return 0;
}

int32_t plugin_execute_command(const PluginCommand *cmd, PluginResponse *resp) {
  strncpy(resp->command_id, cmd->id, PLUGIN_MAX_STRING_LEN - 1);
  strncpy(resp->instrument_name, cmd->instrument_name, PLUGIN_MAX_STRING_LEN - 1);

  if (g_instr == VI_NULL) {
    resp->success = false;
    strncpy(resp->error_message, "VISA not initialized", PLUGIN_MAX_STRING_LEN - 1);
    return -1;
  }

  // Send SCPI command
  viPrintf(g_instr, "%s\n", cmd->verb);

  // If expects response, read it
  if (cmd->expects_response) {
    char buffer[256];
    ViUInt32 ret_count;
    ViStatus status = viScanf(g_instr, "%t", &ret_count, buffer);

    if (status >= VI_SUCCESS) {
      resp->success = true;
      strncpy(resp->text_response, buffer, PLUGIN_MAX_PAYLOAD - 1);
      return 0;
    }

    resp->success = false;
    resp->error_code = status;
    return status;
  }

  resp->success = true;
  return 0;
}

void plugin_shutdown(void) {
  if (g_instr != VI_NULL) {
    viClose(g_instr);
    g_instr = VI_NULL;
  }
  if (g_rm != VI_NULL) {
    viClose(g_rm);
    g_rm = VI_NULL;
  }
}

CMakeLists.txt for VISA plugin:

cmake_minimum_required(VERSION 3.20)
project(VISAPlugin)

find_package(InstrumentServer REQUIRED)

# Find VISA library
find_library(VISA_LIBRARY NAMES visa visa64 PATHS /usr/local/vxipnp/linux64/lib)

add_instrument_plugin(visa_plugin
  SOURCES visa_plugin.c
  LINK_LIBRARIES ${VISA_LIBRARY}
)

install(TARGETS visa_plugin LIBRARY DESTINATION lib/instrument-plugins)

Handling Large Data in Plugins

When to Use Data Buffers

Use data buffers when your plugin returns:

  • Oscilloscope waveforms (>1000 points)
  • Spectrum analyzer traces
  • Camera images
  • Large measurement arrays
  • Any data >1KB

Creating Data Buffers in C Plugins

#include <instrument-server/ipc/DataBufferManager_C.h>

int32_t plugin_execute_command(const PluginCommand *cmd, PluginResponse *resp) {
  // ... execute command and get data ...

  // Example: Large waveform data
  size_t num_points = 10000;
  float *waveform = get_waveform_from_instrument();

  // Create buffer
  char buffer_id[PLUGIN_MAX_STRING_LEN];
  int result = data_buffer_create(
      cmd->instrument_name,
      cmd->id,
      0,  // 0 = FLOAT32, 1 = FLOAT64, 2 = INT32, etc.
      num_points,
      waveform,
      buffer_id
  );

  if (result == 0) {
    // Success - set response
    resp->success = true;
    resp->has_large_data = true;
    strncpy(resp->data_buffer_id, buffer_id, PLUGIN_MAX_STRING_LEN - 1);
    resp->data_element_count = num_points;
    resp->data_type = 0;  // FLOAT32

    snprintf(resp->text_response, PLUGIN_MAX_PAYLOAD,
             "Waveform data in buffer %s", buffer_id);
  } else {
    resp->success = false;
    strncpy(resp->error_message, "Failed to create buffer",
            PLUGIN_MAX_STRING_LEN - 1);
  }

  free(waveform);
  return result;
}

Data Type Codes

// Data type enum values for data_buffer_create()
#define DATA_TYPE_FLOAT32  0
#define DATA_TYPE_FLOAT64  1
#define DATA_TYPE_INT32    2
#define DATA_TYPE_INT64    3
#define DATA_TYPE_UINT32   4
#define DATA_TYPE_UINT64   5
#define DATA_TYPE_UINT8    6

Buffer Lifecycle

  1. Plugin creates buffer - Calls data_buffer_create()
  2. Server receives buffer ID - In PluginResponse
  3. Lua accesses buffer - Via get_buffer()
  4. User exports data - Calls buffer:export_csv() or buffer:export_binary()
  5. User releases buffer - Calls buffer:release()
  6. Auto cleanup - Buffer freed when ref count reaches 0

Linking to DataBufferManager

In your plugin's CMakeLists.txt:

target_link_libraries(your_plugin
  PRIVATE
    InstrumentServerCore  # Provides DataBufferManager C API
)

Example: Oscilloscope Plugin

int32_t get_waveform(const PluginCommand *cmd, PluginResponse *resp) {
  // Query number of points
  uint32_t num_points;
  visa_query(g_session, "WAV: POIN?", &num_points);

  // Allocate temporary buffer
  float *waveform = malloc(num_points * sizeof(float));

  // Read waveform data from instrument
  visa_read_binary(g_session, waveform, num_points);

  // Create shared buffer
  char buffer_id[PLUGIN_MAX_STRING_LEN];
  if (data_buffer_create(cmd->instrument_name, cmd->id,
                        DATA_TYPE_FLOAT32, num_points,
                        waveform, buffer_id) == 0) {
    resp->success = true;
    resp->has_large_data = true;
    strncpy(resp->data_buffer_id, buffer_id, PLUGIN_MAX_STRING_LEN - 1);
    resp->data_element_count = num_points;
    resp->data_type = DATA_TYPE_FLOAT32;
  }

  free(waveform);
  return 0;
}

See Also