SimHub Plugin Integration and Effect Binding Architecture for DIY FFB Pedal
The DIY FFB Pedal SimHub plugin implements a three-layer architecture where IDataPlugin.DataUpdate reads SimHub properties, evaluates NCalc expressions against user-defined bindings stored in DIYFFBPedalSettings, and serializes the results into a DAP_action_st payload transmitted to the pedal via USB serial or ESP-Now.
The DIY Sim Racing FFB Pedal project provides an open-source force-feedback solution that integrates with SimHub to translate live game telemetry into physical pedal effects. Understanding the SimHub plugin integration and effect binding architecture is essential for developers customizing how variables like RPM, wheel slip, and G-force map to vibration and resistance outputs on the hardware.
Plugin Integration Layer
The SimHub plugin entry point is the DIY_FFB_Pedal class declared in SimHubPlugin/DIYFFBPedal.cs. This class implements three critical SimHub interfaces: IPlugin for lifecycle management, IDataPlugin for per-frame data processing, and IWPFSettingsV2 for configuration UI exposure.
Core Interfaces and Entry Points
The plugin metadata is declared using attributes:
[PluginDescription("DIY FFB Pedal Controller")]
[PluginAuthor("chrgri")]
[PluginName("DIY FFB Pedal")]
public class DIY_FFB_Pedal : IPlugin, IDataPlugin, IWPFSettingsV2
{
public PluginManager PluginManager { get; set; }
public void DataUpdate(PluginManager pluginManager, ref GameData data)
{
// Called every simulation frame (~60Hz)
}
public System.Windows.Controls.Control GetWPFSettingsControl(PluginManager pluginManager)
{
return new DIYFFBPedalControlUI(this);
}
}
Data Flow and Property Access
The PluginManager instance provides read-only access to any SimHub property via GetPropertyValue. The plugin stores a reference to this manager (as pluginHandle) and uses it both for direct property lookups and inside NCalc expression evaluation callbacks.
During each frame, the DataUpdate method performs the following sequence:
- Iterates through all three pedals (throttle, brake, clutch).
- Checks enable flags in
Settings(e.g.,RPM_enable_flag[pedalIdx]). - Retrieves bound SimHub properties using
PluginManager.GetPropertyValue. - Evaluates optional NCalc expressions for custom vibrations.
- Populates a
DAP_action_ststructure. - Calculates the CRC checksum using
checksumCalc. - Transmits the payload via
SendPedalAction(USB) orSendPedalActionWireless(ESP-Now).
Effect Binding System
Effect binding connects SimHub telemetry variables to physical pedal outputs through a configurable settings model. The architecture separates storage (DIYFFBPedalSettings), UI representation (WPF controls), and runtime evaluation (DataUpdate).
Settings Model and Profiles
The serializable DIYFFBPedalSettings class in SimHubPlugin/DIYFFBPedalSettings.cs holds all user-tunable parameters:
public class DIYFFBPedalSettings
{
public int[] selectedJsonIndexLast = new int[3] { 0, 3, 6 };
public string[] selectedComPortNames = { "COM1", "COM1", "COM1" };
public uint[] Pedal_action_fps = new byte[3] { 20, 20, 20 };
public bool[,,] Effect_status_prolife = new bool[6, 3, 8]; // profile → pedal → effect
public string WSeffect_bind = ""; // Wheel-Slip property name
public string Road_impact_bind = ""; // Road impact property name
public string[] CV1_bindings = new string[3] { "", "", "" }; // Custom Vibration 1 NCalc expr
public string[] CV2_bindings = new string[3] { "", "", "" }; // Custom Vibration 2 NCalc expr
// Additional flags: ABS_enable_flag, RPM_enable_flag, G_force_enable_flag, etc.
}
The profile UI (SystemSetting_Profiles.xaml.cs) renders Effect_status_prolife as a grid of check-boxes. When a user toggles an effect for a specific pedal and profile, the underlying 3-D boolean array updates, which DataUpdate later references to determine which effects to evaluate.
Mapping SimHub Variables to Pedal Effects
Inside DataUpdate, the plugin evaluates each enabled effect by retrieving the bound SimHub property:
// Wheel-slip effect
if (Settings.WS_enable_flag[pedalIdx] == 1)
{
WS_value = Convert.ToByte(pluginManager.GetPropertyValue(Settings.WSeffect_bind));
if (WS_value >= Settings.WS_trigger)
tmp.payloadPedalAction_.WS_u8 = 1;
}
// Road impact effect
if (Settings.Road_impact_enable_flag[pedalIdx] == 1)
{
Road_impact_value = Convert.ToByte(pluginManager.GetPropertyValue(Settings.Road_impact_bind));
tmp.payloadPedalAction_.impact_value = Road_impact_value;
}
// G-force effect
if (Settings.G_force_enable_flag[pedalIdx] == 1)
{
// Calculated from game data or direct property
tmp.payloadPedalAction_.G_value = (Byte)g_force_last_value;
}
NCalc Expression Evaluation
For advanced users, Custom Vibration 1 and 2 support NCalc mathematical expressions. The Ncalc_reading helper (defined in DIY_FFB_Pedal.cs) evaluates strings like Math.Max(0, data.NewData.SpeedKmh - 80) at runtime, automatically resolving property names via the stored PluginManager reference.
if (Settings.CV1_enable_flag[pedalIdx])
{
string expr = Settings.CV1_bindings[pedalIdx];
string result = Ncalc_reading(expr);
if (double.TryParse(result, out double cv1) && cv1 > Settings.CV1_trigger[pedalIdx])
tmp.payloadPedalAction_.Trigger_CV_1 = 1;
}
This allows complex logic—such as scaling vibration intensity with vehicle speed or combining multiple telemetry values—without recompiling the plugin.
Payload Construction and Transmission
Once effects are evaluated, the plugin constructs a binary payload defined by the DAP_action_st struct (located in SimHubPlugin/VariablesStruct/DAP_action_st.cs). This structure contains a header, the action payload, and a checksum footer.
Building the Action Structure
DAP_action_st tmp = new DAP_action_st();
tmp.payloadHeader_.version = (byte)Constants.pedalConfigPayload_version;
tmp.payloadHeader_.payloadType = (byte)Constants.pedalActionPayload_type;
tmp.payloadHeader_.PedalTag = (byte)pedalIdx;
// Populate effect fields evaluated above
tmp.payloadPedalAction_.RPM_u8 = (Byte)RPM_value;
tmp.payloadPedalAction_.G_value = (Byte)g_force_last_value;
tmp.payloadPedalAction_.WS_u8 = WS_value;
// ... additional fields ...
Checksum and Serialization
The plugin calculates a CRC checksum over the header and payload using checksumCalc, then appends it to the footer:
DAP_action_st* v = &tmp;
byte* p = (byte*)v;
tmp.payloadFooter_.checkSum = checksumCalc(p, sizeof(payloadHeader) + sizeof(payloadPedalAction));
The getBytes_Action method serializes the structure into a byte array for transmission.
Transport Layer
The plugin supports dual transport methods selected per-pedal via Settings.Pedal_ESPNow_Sync_flag:
- USB Serial:
SendPedalAction(tmp, (byte)pedalIdx)writes to the COM port specified inSettings.selectedComPortNames. - ESP-Now Wireless:
SendPedalActionWireless(tmp, (byte)pedalIdx)transmits via the ESP-Now protocol for wireless pedal setups.
Both methods use the identical DAP_action_st payload, ensuring consistent behavior regardless of connection type.
Extending the Architecture: Adding a Custom Effect
To demonstrate the extensibility of the SimHub plugin integration and effect binding architecture, consider adding a "Turbo Boost" effect that triggers when vehicle speed exceeds 150 km/h.
Step 1: Extend the Settings Model
Add the configuration fields to DIYFFBPedalSettings.cs:
public int[] TurboBoost_enable_flag = new int[3] { 0, 0, 0 };
public string TurboBoost_bind = ""; // SimHub property name
public int TurboBoost_trigger = 150; // Speed threshold in km/h
Step 2: Update the UI
Add a check-box and text field in the appropriate XAML file (e.g., SystemSetting_Profiles.xaml) bound to TurboBoost_enable_flag and TurboBoost_bind.
Step 3: Implement Runtime Logic
Insert the evaluation logic inside the DataUpdate method in DIY_FFB_Pedal.cs:
// Turbo boost effect
if (Settings.TurboBoost_enable_flag[pedalIdx] == 1)
{
int speed = Convert.ToInt32(pluginManager.GetPropertyValue(Settings.TurboBoost_bind));
if (speed > Settings.TurboBoost_trigger)
{
// Reuse a spare payload field or extend payloadPedalAction
tmp.payloadPedalAction_.Trigger_CV_1 = 1;
update_flag = true;
}
}
Step 4: Rebuild and Deploy
Recompile the plugin. The new effect appears in the profile configuration UI and automatically participates in the checksum calculation and transmission pipeline without requiring changes to the serial or wireless transport layers.
Summary
- Entry Point: The
DIY_FFB_Pedalclass inDIYFFBPedal.csimplementsIDataPluginandIWPFSettingsV2to receive per-frame game data and expose configuration UI. - Data Retrieval: The
PluginManager.GetPropertyValuemethod provides access to any SimHub telemetry variable (RPM, G-force, wheel-slip) inside theDataUpdateloop. - Effect Configuration: The
DIYFFBPedalSettingsclass stores boolean flags, property bindings, and NCalc expressions, while WPF controls inSystemSetting_Profiles.xaml.csprovide the user interface. - Runtime Evaluation:
DataUpdatechecks enable flags, retrieves bound properties, evaluates NCalc expressions viaNcalc_reading, and populates theDAP_action_stpayload. - Transmission: The
SendPedalActionandSendPedalActionWirelessmethods transmit the serialized payload over USB or ESP-Now after CRC calculation viachecksumCalc.
Frequently Asked Questions
How does the DIY FFB Pedal plugin access SimHub telemetry data?
The plugin receives a PluginManager instance from SimHub during initialization and stores it as pluginHandle. Inside the DataUpdate method, it calls pluginManager.GetPropertyValue(string name) to retrieve any telemetry variable—such as DataCorePlugin.GameData.RPM or DataCorePlugin.GameRawData.CarSpeed—by property name.
What is the purpose of the NCalc integration in the effect binding system?
NCalc allows users to define mathematical expressions that are evaluated at runtime against SimHub data, enabling complex effect triggers without code changes. The Ncalc_reading method in DIY_FFB_Pedal.cs parses expressions like Math.Max(0, SpeedKmh - 80) and automatically resolves property names using the stored PluginManager reference, returning a numeric result that DataUpdate compares against trigger thresholds.
Can the plugin send force-feedback commands to pedals wirelessly?
Yes. The architecture abstracts transport logic so the same DAP_action_st payload can be transmitted over USB serial or ESP-Now wireless. The DataUpdate method checks Settings.Pedal_ESPNow_Sync_flag[pedalIdx] to determine the transport mode and calls either SendPedalAction for wired connections or SendPedalActionWireless for wireless transmission, both performing CRC calculation via checksumCalc before sending.
How do I add a new custom effect without modifying the core plugin logic?
To add a new effect, extend DIYFFBPedalSettings.cs with enable flags and binding strings (e.g., public int[] MyEffect_enable_flag and public string MyEffect_bind). Add corresponding UI controls in the XAML files (such as SystemSetting_Profiles.xaml.cs). Finally, insert evaluation logic inside the DataUpdate method in DIY_FFB_Pedal.cs to check your flag, retrieve the property via PluginManager.GetPropertyValue, and set the appropriate field in tmp.payloadPedalAction_. The existing checksum and transmission infrastructure handles the new payload automatically.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →