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.
| Image | File | Purpose |
|---|---|---|
| icon.png | Icon 32 x 32 (for Sidebar & Header) | |
| thumbnail.png | Thumbnail 228 x 86 (for game profiles) | |
![]() | featured.png | Image 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 Type | Recommended Rate |
|---|---|
| LCD dashboard | 30-60 Hz |
| LEDs | 30-100 Hz |
| motion platform | 200-1000 Hz |
| telemetry logger | 10-60 Hz |
| network sender | 30-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:
| Setting | Value |
|---|---|
| Visual Studio | 2019 |
| Language Standard | C++17 |
| Runtime | Multi-threaded DLL |
| Configuration Type | Dynamic Library (.dll) |
| Character Set | Unicode |
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.

