Skip to main content
Custom simulation extensions allow you to simulate specialized hardware, implement custom protocols, or create unique testing environments for your robot code.

Extension Structure

A simulation extension is a dynamically loaded library (.dll on Windows, .so on Linux, .dylib on macOS) with a specific entry point that the robot program calls during initialization.

Minimum Extension Code

Every extension must implement the HALSIM_InitExtension function:
#include <hal/Extensions.h>
#include <cstdio>

extern "C" {
#if defined(WIN32) || defined(_WIN32)
__declspec(dllexport)
#endif
int HALSIM_InitExtension(void) {
  std::puts("My Extension Initializing");
  
  // Initialize your extension here
  // Register callbacks, set up connections, etc.
  
  std::puts("My Extension Initialized");
  return 0; // Success
}
}

Function Requirements

  • Return value: 0 for success, -1 for failure
  • extern “C”: Prevents C++ name mangling
  • __declspec(dllexport): Required on Windows for DLL exports
  • No parameters: The function takes no arguments

Complete Extension Example

Here’s a more complete example based on the XRP extension:
// MyCustomExtension.cpp
#include <cstdio>
#include <memory>
#include <hal/Extensions.h>
#include <hal/simulation/DriverStationData.h>

class MySimExtension {
public:
  bool Initialize() {
    // Register callback for robot state changes
    HALSIM_RegisterDriverStationEnabledCallback(
        [](const char* name, void* param, const HAL_Value* value) {
          bool enabled = value->data.v_boolean;
          printf("Robot %s\n", enabled ? "enabled" : "disabled");
        },
        nullptr,
        true);
    
    return true;
  }
  
  void Shutdown() {
    std::puts("Extension shutting down");
  }
};

static std::unique_ptr<MySimExtension> gExtension;

extern "C" {
#if defined(WIN32) || defined(_WIN32)
__declspec(dllexport)
#endif
int HALSIM_InitExtension(void) {
  std::puts("My Custom Extension Initializing");
  
  // Register shutdown callback
  HAL_OnShutdown(nullptr, [](void*) { 
    gExtension.reset(); 
  });
  
  gExtension = std::make_unique<MySimExtension>();
  if (!gExtension->Initialize()) {
    return -1;
  }
  
  std::puts("My Custom Extension Initialized");
  return 0;
}
}

Using HALSIM Callback Functions

Available Device APIs

Simulation functions are available for many HAL devices in hal/simulation/:
  • AccelerometerData: Update accelerometer X, Y, Z values
  • AnalogInData: Set analog input voltages
  • DIOData: Control digital I/O states
  • EncoderData: Update encoder counts and rates
  • GyroData: Set gyro angles and rates
  • I2CData: Simulate I2C device communication
  • PWMData: Monitor PWM outputs
  • SPIData: Simulate SPI device communication
  • SimDeviceData: Create custom simulation devices

Registering Callbacks

Callbacks allow you to respond to HAL data changes:
#include <hal/simulation/EncoderData.h>

int32_t callbackId = HALSIM_RegisterEncoderCountCallback(
    encoderIndex,
    [](const char* name, void* param, const HAL_Value* value) {
      int32_t count = value->data.v_int;
      printf("Encoder count: %d\n", count);
    },
    nullptr,  // User parameter
    true);    // Initial callback

Setting HAL Values

Update simulated hardware values from your extension:
#include <hal/simulation/AnalogInData.h>
#include <hal/simulation/DIOData.h>

// Set analog input voltage
HALSIM_SetAnalogInVoltage(channel, 3.3);

// Set digital input state
HALSIM_SetDIOValue(channel, true);

// Update gyro angle
HALSIM_SetGyroAngle(0, 45.0);

Building Your Extension

Setup build.gradle

Create a build.gradle file for your extension. Use the XRP extension as a template:
description = "My Custom Simulation Extension"

ext {
    includeWpiutil = true
    pluginName = 'halsim_my_extension'
}

apply from: "${rootDir}/shared/plugins/setupBuild.gradle"

model {
    binaries {
        all {
            // Don't build for roboRIO
            if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
                it.buildable = false
                return
            }
            
            // Link with WPILib libraries
            lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
            
            // If you need websockets support
            // lib project: ":simulation:halsim_ws_core", 
            //     library: "halsim_ws_core", linkage: "static"
        }
    }
}

Key Configuration

  • pluginName: Must match your extension’s library name
  • includeWpiutil: Include WPIUtil library
  • linkage: ‘shared’: Link with shared libraries (important!)
  • roborio check: Disable building for roboRIO platform

Building the Extension

1

Place in allwpilib

Put your extension directory in allwpilib/simulation/
2

Build WPILib

Build the entire WPILib project:
./gradlew build
3

Publish Locally

Publish to your local Maven repository:
./gradlew publish

Using Your Custom Extension

In a Robot Project

After publishing WPILib locally, add your extension to a robot project’s build.gradle:
wpi.sim.addDep(
    "My Custom Extension",           // Display name
    "edu.wpi.first.halsim",          // Group ID
    "halsim_my_extension"             // Plugin name from build.gradle
)

Configure Robot Project

Follow the development builds guide to configure your robot project to use your local WPILib build. Add to build.gradle:
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = '2025.0.0-development'

Extension Communication

Registering Your Extension

Allow other extensions to detect your extension:
struct MyExtensionData {
  void (*SendCommand)(const char* cmd);
  int (*GetStatus)();
};

MyExtensionData data = {
  .SendCommand = &SendCommandImpl,
  .GetStatus = &GetStatusImpl
};

HAL_RegisterExtension("my_extension", &data);

Listening for Other Extensions

Detect when specific extensions are loaded:
HAL_RegisterExtensionListener(
    nullptr, 
    [](void*, const char* name, void* data) {
      if (std::string_view{name} == "halsim_gui") {
        // GUI extension loaded, can interact with it
        auto* guiData = static_cast<GuiExtensionData*>(data);
        guiData->RegisterWindow("My Window", &CreateWindow);
      }
    });

Advanced: I2C/SPI Simulation

Simulate real I2C or SPI devices:
#include <hal/simulation/I2CData.h>

// Register I2C read callback
HALSIM_RegisterI2CReadCallback(
    port,
    [](const char* name, void* param, 
       const uint8_t* buffer, uint32_t count) -> int32_t {
      // Simulate device response
      if (buffer[0] == 0x00) { // Read register 0
        uint8_t response[2] = {0x12, 0x34};
        HALSIM_SetI2CReadData(port, response, 2);
        return 2;
      }
      return 0;
    },
    nullptr);

Performance Considerations

Callback Performance

Callbacks run synchronously in the robot program thread:
  • Keep callbacks fast - avoid heavy computation
  • Don’t block - no long delays or I/O waits
  • Process data in background threads if needed
// GOOD: Quick callback
HALSIM_RegisterEncoderCountCallback(
    index,
    [](const char* name, void* param, const HAL_Value* value) {
      quickUpdate(value->data.v_int);
    },
    nullptr, true);

// BAD: Slow callback - blocks robot thread!
HALSIM_RegisterEncoderCountCallback(
    index,
    [](const char* name, void* param, const HAL_Value* value) {
      std::this_thread::sleep_for(100ms); // Don't do this!
      expensiveCalculation();
    },
    nullptr, true);

Use Background Processing

For heavy operations:
std::queue<int32_t> dataQueue;
std::mutex queueMutex;

// Quick callback - just queue the data
HALSIM_RegisterCallback(..., 
    [](const char* name, void* param, const HAL_Value* value) {
      std::lock_guard<std::mutex> lock(queueMutex);
      dataQueue.push(value->data.v_int);
    }, ...);

// Process in background thread
std::thread([&]() {
  while (running) {
    std::lock_guard<std::mutex> lock(queueMutex);
    while (!dataQueue.empty()) {
      processData(dataQueue.front());
      dataQueue.pop();
    }
  }
}).detach();

Example Extensions in WPILib

Study these built-in extensions for reference:
  • halsim_xrp: Simple client protocol implementation
  • halsim_ws_server: WebSocket server with HTTP support
  • halsim_gui: Complex GUI with ImGui integration
  • halsim_ds_socket: Driver Station protocol implementation
Source code: allwpilib/simulation/halsim_*/

Next Steps

Simulation Overview

Review the simulation architecture

Running Examples

Test your extension with example programs

Build docs developers (and LLMs) love