Creating an OUT Plugin

This chapter explains how to create an OpenSR OUT plugin in C++ using the IOpenSRPlugin interface.

Unlike GAME plugins, OUT plugins do not produce telemetry.

Instead, they consume telemetry already normalized by OpenSR and use it to:

  • drive external hardware
  • feed displays
  • control dashboards
  • send telemetry over network protocols
  • interface with motion systems
  • generate telemetry logs
  • monitor telemetry
  • forward telemetry to external applications
  • render UI
  • expose monitoring/debugging tools

This chapter uses the Monitor Telemetry Plugin from the OpenSR SDK as a production example.

The goal is NOT to teach how to create a telemetry monitor specifically.

The goal is to teach how to create a robust OUT plugin architecture that can later be adapted for:

  • LCD dashboards
  • motion platforms
  • LEDs
  • CAN devices
  • Arduino controllers
  • UDP forwarders
  • serial devices
  • telemetry analyzers
  • web telemetry services
  • custom hardware
  • stream overlays
  • debugging tools

1. OUT Plugin Role

A GAME plugin writes telemetry into OpenSR.

An OUT plugin reads telemetry from OpenSR.

Game Plugin
    ↓
OpenSR Core
    ↓
OUT Plugin

The OUT plugin never talks directly to the game.

It only consumes standardized OutSimData.

This abstraction is one of the most important OpenSR concepts.

It means your hardware/device/service plugin works with:

  • BeamNG
  • Assetto Corsa
  • iRacing
  • ACC
  • AMS2
  • RF2
  • custom games

without rewriting your device logic.


2. OUT Plugin File Extension

OUT plugins use:

.osro

Example:

OpenSRMonitorPlugin.osro

GAME plugins use .osri.


3. SDK Location

If the SDK component was installed from the OpenSR installer:

Documents/OpenSR/OpenSR_SDK1.0/

Typical structure:

OpenSR_SDK1.0/
│
├── includes/
├── examples/
└── docs/

4. Plugin Installation Directory

OUT plugins are installed inside:

Documents/OpenSR/Plugins/

Example:

Documents/OpenSR/Plugins/OpenSRMonitorPlugin/

Typical structure:

OpenSRMonitorPlugin/
│
├── OpenSRMonitorPlugin.osro
├── settings.xml
├── icon.png
├── thumbnail.png
├── featured.png
└── ui.dll (optional)

5. Mandatory Images

Every plugin should provide standardized artwork.

ImageFilePurpose
icon.pngIcon 32 x 32 (for Sidebar & Header)
thumbnail.pngThumbnail 228 x 86 (for game profiles)
featured.pngImage 512 x 282 (for Info page)

These images are used by the OpenSR dashboard and plugin manager.


6. OUT Plugin Architecture

The monitor plugin demonstrates several important production concepts:

  • OUT telemetry consumption
  • runtime threads
  • Win32 UI
  • telemetry rendering
  • XML settings
  • runtime settings reload
  • pause/resume synchronization
  • external windows
  • dashboard companion UI modules
  • telemetry dumping
  • plugin-managed resources

7. Basic OUT Plugin Skeleton

Minimal structure:

class MyOutPlugin : public osr::IOpenSRPlugin
{
public:

    bool Init(
        OpenSRContext* context,
        void* outBuf,
        const wchar_t* pluginPath) override;

    bool Start() override;

    void Stop() override;

    void Shutdown() override;

    void Pause() override;

    void Resume() override;

    bool IsRunning() const override;

    int GetType() override
    {
        return OUT_PLUGIN_TYPE;
    }
};

8. OUT Plugin Metadata

Metadata is mandatory.

Without metadata the dashboard cannot identify the plugin correctly.

Example from the monitor plugin:

void GetPluginName(
    char* buffer,
    size_t bufferSize) const override
{
    strcpy_s(
        buffer,
        bufferSize,
        "Monitor Telemetry (Example)");
}

Required metadata:

GetPluginName()
GetAuthor()
GetVersion()
GetLicenseType()
GetDescription()
GetPackageName()
GetType()

OUT plugins usually leave these empty:

GetTargetName()
GetTargetProcessName()

because they are not tied to a specific game executable.


9. Factory Exports

Every OUT plugin must export:

extern "C"
__declspec(dllexport)
osr::IOpenSRPlugin* CreatePlugin()
{
    return new MyOutPlugin();
}

extern "C"
__declspec(dllexport)
void DestroyPlugin(
    osr::IOpenSRPlugin* plugin)
{
    delete plugin;
}

These exports are mandatory.

OpenSR loads plugins dynamically through them.


10. Init()

Init() is called once after the plugin is loaded.

Typical responsibilities:

  • store context pointers
  • store shared buffers
  • store plugin path
  • create directories
  • prepare resources
  • build settings paths

Example:

bool OpenSRMonitorPlugin::Init(
    OpenSRContext* context,
    void* outBuf,
    const wchar_t* pluginPath)
{
    m_pContext = context;

    m_pBuffersOut =
        static_cast<OpenSRBuffersOUT*>(outBuf);

    m_pluginPath = pluginPath;

    return true;
}

11. Accessing Telemetry

OUT plugins read telemetry through:

m_pBuffersOut->outSimDataOUT

Example:

OutSimData* outSim =
    m_pBuffersOut->outSimDataOUT;

This structure contains normalized telemetry from all supported games.


12. Reading Telemetry

Example:

float speed =
    outSim->mVehicleData.speed;

float rpm =
    outSim->mVehicleData.rpm;

int gear =
    outSim->mVehicleData.gear;

This telemetry is already normalized by GAME plugins.

Your OUT plugin should NEVER care which game generated it.


13. Worker Threads

OUT plugins usually run one or more worker threads.

Examples:

  • serial writer thread
  • UDP sender thread
  • USB polling thread
  • display refresh thread
  • Win32 UI thread
  • telemetry logger thread

The monitor plugin uses a dedicated window thread:

windowThread_ =
    std::thread(
        &OpenSRMonitorPlugin::WindowThreadFunc,
        this);

14. Pause / Resume

OUT plugins must support pause/resume.

Example:

void Pause() override
{
    m_isPluginPaused = true;
}
void Resume() override
{
    m_isPluginPaused = false;

    m_pauseCv.notify_one();
}

This allows OpenSR to synchronize all plugins consistently.


15. Start()

Recommended responsibilities:

  • load settings
  • start threads
  • initialize devices
  • initialize sockets
  • open serial ports
  • create windows
  • initialize SDKs

Example:

bool OpenSRMonitorPlugin::Start()
{
    if (!m_pContext)
        return false;

    m_stopRequested = false;

    m_isPluginRunning = true;

    windowThread_ =
        std::thread(
            &OpenSRMonitorPlugin
                ::WindowThreadFunc,
            this);

    return true;
}

16. Stop()

Stop() must safely stop everything.

Typical responsibilities:

  • stop threads
  • close sockets
  • close serial devices
  • stop timers
  • destroy windows
  • stop SDKs
  • join worker threads

Example:

void OpenSRMonitorPlugin::Stop()
{
    m_stopRequested = true;

    if (windowThread_.joinable())
    {
        windowThread_.join();
    }

    m_isPluginRunning = false;
}

17. Runtime Settings

OUT plugins can provide optional:

settings.xml

Example:

<settings>
    <option
        name="device update delay (ms)"
        tooltip="update interval"
        type="slider"
        range="10,1000">200</option>
</settings>

18. Loading Settings

The monitor plugin uses TinyXML2.

Include:

#include "tinyxml2.h"

using namespace tinyxml2;

Example:

struct PluginSettings
{
    int deviceRate = 200;
};

Example loader:

bool LoadPluginSettings(
    const std::wstring& filepath,
    PluginSettings& s)
{
    XMLDocument doc;

    FILE* file =
        _wfsopen(
            filepath.c_str(),
            L"rb",
            _SH_DENYNO);

    if (!file)
        return false;

    if (doc.LoadFile(file) != XML_SUCCESS)
    {
        fclose(file);
        return false;
    }

    fclose(file);

    XMLElement* root =
        doc.FirstChildElement("settings");

    if (!root)
        return false;

    for (XMLElement* opt =
            root->FirstChildElement("option");
         opt;
         opt = opt->NextSiblingElement("option"))
    {
        const char* name =
            opt->Attribute("name");

        if (!name)
            continue;

        if (strcmp(
                name,
                "device update delay (ms)") == 0)
        {
            s.deviceRate =
                opt->IntText(200);
        }
    }

    return true;
}

19. Building Settings Path

Recommended pattern:

std::wstring spath =
    StringUtils::createWString(
        m_pContext->osrDocFolder,
        L"\\",
        m_pluginPath,
        L"\\settings.xml");

This guarantees compatibility with OpenSR folder conventions.


20. Runtime Settings Reload

Recommended approach:

void OnContextChanged(
    osr::OpenSRContextChange reason)
{
    if (reason ==
        osr::OpenSRContextChange
            ::SettingsChanged)
    {
        Stop();
        Start();
    }
}

Recommended practice:

  • reload settings inside Start()
  • restart runtime cleanly
  • recreate sockets/devices safely
  • avoid partial reload states

21. Rendering Windows

The monitor plugin demonstrates a full Win32 rendering loop.

This is optional.

Most OUT plugins do not need UI.

Typical OUT plugins instead:

  • send serial packets
  • send UDP packets
  • write HID reports
  • update LEDs
  • control displays

22. External UI vs Dashboard UI

The monitor plugin supports two UI approaches:

External Window

Standalone runtime window.

Dashboard UI Module

Separate ui.dll loaded directly inside the OpenSR dashboard.

This separation is recommended:

Runtime Device Logic
    ≠
Dashboard UI Logic

23. Telemetry Rates

OUT plugins should usually NOT run at unrestricted speed.

Typical device update rates:

Device TypeRecommended Rate
LCD dashboard30-60 Hz
LEDs30-100 Hz
motion platform200-1000 Hz
telemetry logger10-60 Hz
network sender30-120 Hz

The monitor plugin demonstrates configurable update timing.


24. Accessing All Telemetry Categories

The monitor plugin demonstrates access to:

  • packet header
  • players
  • vehicle data
  • wheel data
  • brakes
  • suspensions
  • environment
  • motion data

This makes it an excellent SDK reference.


25. OUT Plugins Can Produce Inputs

OUT plugins may also fill the Input structure.

Examples:

  • wheel buttons
  • custom button boxes
  • encoders
  • switches
  • axes

This is explained in the SDK architecture chapters.


26. Recommended OUT Plugin Architecture

Production recommendations:

Main Plugin
    ├── Device Thread
    ├── Telemetry Thread
    ├── UI Thread
    ├── Settings Loader
    ├── Protocol Encoder
    └── Device SDK Wrapper

Keep hardware/protocol logic separated from plugin lifecycle logic.


27. Recommended Visual Studio Settings

Recommended:

SettingValue
Visual Studio2019
Language StandardC++17
RuntimeMulti-threaded DLL
Configuration TypeDynamic Library (.dll)
Character SetUnicode

After build:

.dll → .osro

28. Final Recommendations

A robust OUT plugin should:

  • never block the OpenSR core
  • use worker threads
  • safely stop all threads
  • support pause/resume
  • support runtime settings reload
  • validate device communication
  • tolerate telemetry loss
  • tolerate game switching
  • avoid hardcoded game logic
  • rely only on normalized OpenSR telemetry

The monitor plugin is intentionally complex because it demonstrates many real production patterns used by advanced OUT plugins.