# SimHub Plugin Integration and Effect Binding Architecture for DIY FFB Pedal

> Explore the DIY FFB Pedal SimHub plugin architecture for effect binding. Discover how data updates are processed and transmitted via USB serial or ESP-Now for your sim racing setup.

- Repository: [chrgri/diy-sim-racing-ffb-pedal](https://github.com/chrgri/diy-sim-racing-ffb-pedal)
- Tags: architecture
- Published: 2026-02-27

---

**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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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:

```csharp
[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:

1.  Iterates through all three pedals (throttle, brake, clutch).
2.  Checks enable flags in `Settings` (e.g., `RPM_enable_flag[pedalIdx]`).
3.  Retrieves bound SimHub properties using `PluginManager.GetPropertyValue`.
4.  Evaluates optional NCalc expressions for custom vibrations.
5.  Populates a `DAP_action_st` structure.
6.  Calculates the CRC checksum using `checksumCalc`.
7.  Transmits the payload via `SendPedalAction` (USB) or `SendPedalActionWireless` (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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/SimHubPlugin/DIYFFBPedalSettings.cs) holds all user-tunable parameters:

```csharp
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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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:

```csharp
// 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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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.

```csharp
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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/SimHubPlugin/VariablesStruct/DAP_action_st.cs)). This structure contains a header, the action payload, and a checksum footer.

### Building the Action Structure

```csharp
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:

```csharp
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 in `Settings.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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/DIYFFBPedalSettings.cs):

```csharp
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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/DIY_FFB_Pedal.cs):

```csharp
// 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_Pedal` class in [`DIYFFBPedal.cs`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/DIYFFBPedal.cs) implements `IDataPlugin` and `IWPFSettingsV2` to receive per-frame game data and expose configuration UI.
- **Data Retrieval**: The `PluginManager.GetPropertyValue` method provides access to any SimHub telemetry variable (RPM, G-force, wheel-slip) inside the `DataUpdate` loop.
- **Effect Configuration**: The `DIYFFBPedalSettings` class stores boolean flags, property bindings, and NCalc expressions, while WPF controls in [`SystemSetting_Profiles.xaml.cs`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/SystemSetting_Profiles.xaml.cs) provide the user interface.
- **Runtime Evaluation**: `DataUpdate` checks enable flags, retrieves bound properties, evaluates NCalc expressions via `Ncalc_reading`, and populates the `DAP_action_st` payload.
- **Transmission**: The `SendPedalAction` and `SendPedalActionWireless` methods transmit the serialized payload over USB or ESP-Now after CRC calculation via `checksumCalc`.

## 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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/SystemSetting_Profiles.xaml.cs)). Finally, insert evaluation logic inside the `DataUpdate` method in [`DIY_FFB_Pedal.cs`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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.