# Sensor Checker Dynamic Loading System

This system implements a plugin-based architecture for sensor parameter validation using dynamic shared object loading. It allows different sensor types to have their own validation logic while maintaining a common interface.

## Architecture Overview

The system consists of:

1. **Core Interface** (`ISensorCheckerImpl.h`) - Defines the common interface that all sensor checker implementations must follow
2. **Type Definitions** (`SensorCheckerTypes.h`) - Defines the data structures used by the API
3. **Utility Class** (`SensorCheckerUtil.h/cpp`) - Main class that handles dynamic loading and parameter caching
4. **Implementation Libraries** - Separate shared object files for each sensor type
5. **Factory Functions** - C-style functions exported by each implementation library
6. **Python Bindings** - Python interface for easy integration

## How Dynamic Loading Works

### 1. Library Discovery
When `SensorCheckerUtil::init()` is called, it:
- Constructs the library path based on the model information (name, version, schema version, vendor, market)
- Or uses a custom path if provided
- Loads the shared library using platform-specific APIs (`dlopen` on Linux, `LoadLibrary` on Windows)

### 2. Symbol Resolution
The system looks for a factory function named `createSensorCheckerImpl()` in the loaded library:
```cpp
extern "C" {
    SENSOR_CHECKER_API omni::sensors::ISensorCheckerImpl* createSensorCheckerImpl();
    SENSOR_CHECKER_API void destroySensorCheckerImpl(omni::sensors::ISensorCheckerImpl* impl);
}
```

### 3. Implementation Creation
Once the factory function is found, it's called to create an instance of the sensor checker implementation.

### 4. Parameter Validation
The implementation's `validateParams()` method is called with the provided parameters. It returns `nullptr` if valid, or an error string if validation fails.

## Package Structure

```
sensor_checker/
├── docs/
│   └── README.md                     # This documentation file
├── include/
│   ├── ISensorCheckerImpl.h          # Common interface for sensor checker implementations
│   ├── SensorCheckerTypes.h          # Type definitions and enums
│   └── SensorCheckerUtil.h           # Main utility class header
├── python/
│   ├── __init__.py                   # Python module entry point
│   └── test_sensor_checker.py        # Python test suite
├── PACKAGE-LICENSES/                 # Package license files
```

## Data Structures

### ModelInfo
Contains information about the sensor model:
```cpp
struct ModelInfo
{
    const char* modelName;      // Name of the sensor model (e.g., "LidarCore")
    const char* modelVersion;   // Version of the model (e.g., "1.0")
    const char* modelVendor;    // Vendor of the model (e.g., "NVIDIA")
    const char* schemaVersion;  // Version of the schema (e.g., "1.0")
    const char* marketName;     // Market name for the model
};
```

### Parameters
Contains the parameters to be validated:
```cpp
struct Parameters
{
    uint32_t numParams;                    // Number of parameters
    const char** paramNames;               // Array of parameter names
    SensorCheckerParamType* dataTypes;     // Array of parameter data types
    uint32_t* paramVectorLengths;          // Array of vector lengths
    const void** paramValues;              // Array of parameter values
};
```

### AOVInfo
Contains information about an Arbitrary Output Variable:
```cpp
struct AOVInfo
{
    const char* aovName;        // Name of the AOV
    const void* aovBuffer;      // Buffer containing AOV data
    const void* aovMetadata;    // Metadata for the AOV
};
```

## Usage Examples

### C++ Usage
```cpp
#include "SensorCheckerUtil.h"

omni::sensors::SensorCheckerUtil checker;

// Initialize with model information
omni::sensors::ModelInfo modelInfo = {
    "LidarCore",    // modelName
    "1.0",          // modelVersion
    "NVIDIA",       // modelVendor
    "1.0",          // schemaVersion
    "default"       // marketName
};
checker.init(modelInfo);

// Prepare parameters -- this is an incomplete set just for demonstration
const char* paramNames[] = {"omni:sensor:Core:numberOfEmitters", "omni:sensor:Core:nearRange"};
omni::sensors::SensorCheckerParamType dataTypes[] = {
    omni::sensors::SensorCheckerParamType::INT,
    omni::sensors::SensorCheckerParamType::FLOAT
};
uint32_t paramVectorLengths[] = {1, 1};
uint32_t numEmitters = 100U;
float nearRange = 0.5f;
const void* paramValues[] = {&numEmitters, &nearRange};

omni::sensors::Parameters params = {
    2,              // numParams
    paramNames,     // paramNames
    dataTypes,      // dataTypes
    paramVectorLengths, // paramVectorLengths
    paramValues     // paramValues
};

// Validate parameters
const char* error = checker.validateParams(params);

if (error == nullptr) {
    auto validatedParams = checker.getValidatedParams();
    // Use validated parameters...
} else {
    std::cout << "Validation failed: " << error << std::endl;
}
```

### USD File Validation
```cpp
// Validate parameters from a USD file
const char* error = checker.validateParams("/path/to/sensor.usda", "/sensor_prim");
if (error == nullptr) {
    std::cout << "USD validation successful" << std::endl;
} else {
    std::cout << "USD validation failed: " << error << std::endl;
}
```

### AOV Validation
```cpp
// First the parameters needed for AOV validation have to be cached and validated
const char* error = checker.validateParams("/path/to/sensor.usda", "/sensor_prim");
if (error != nullptr) {
    std::cout << "Parameter validation failed: " << error << std::endl;
    return;
}

// Validate AOV (Arbitrary Output Variable)
omni::sensors::AOVInfo aovInfo = {
    "Coordinates",  // aovName
    aovBuffer,      // aovBuffer
    aovMetadata     // aovMetadata
};

// Get the validated parameters for AOV validation
auto validatedParams = checker.getValidatedParams();
const char* aovError = checker.validateAOV(aovInfo);
if (aovError == nullptr) {
    std::cout << "AOV validation successful" << std::endl;
} else {
    std::cout << "AOV validation failed: " << aovError << std::endl;
}
```

### Python Usage
```python
from omni.sensors.sensor_checker import SensorCheckerUtil, ModelInfo, Parameters, SensorCheckerParamType

# Create a sensor checker
checker = SensorCheckerUtil()

# Initialize with LidarCore model
model_info = ModelInfo(
    model_name="LidarCore",
    model_version="1.0",
    model_vendor="NVIDIA",
    schema_version="1.0",
    market_name="default"
)
checker.init(model_info)

# USDA path and to be checked prim
usda_path = "path/to/usda.usda"
prim_path = "/prim_path"
# Validate the parameters
error = checker.validateParams(usda_path,prim_path)
if error is None:
    print("LidarCore parameters valid")
    validated_params = checker.getValidatedParams()
    print("Validated parameters:", validated_params)
else:
    print(f"Validation failed: {error}")
```

## Creating New Sensor Checker Implementations

### 1. Create Implementation Class
```cpp
class MySensorCheckerImpl : public omni::sensors::ISensorCheckerImpl
{
public:
    void init(const omni::sensors::ModelInfo& modelInfo) override;
    const char* validateParams(const omni::sensors::Parameters& params) override;
    const char* validateAOV(const omni::sensors::AOVInfo& aovInfo, 
                           const omni::sensors::Parameters& activeParams) override;
};
```

### 2. Implement Factory Functions
```cpp
extern "C" {
    SENSOR_CHECKER_API omni::sensors::ISensorCheckerImpl* createSensorCheckerImpl()
    {
        return new MySensorCheckerImpl();
    }

    SENSOR_CHECKER_API void destroySensorCheckerImpl(omni::sensors::ISensorCheckerImpl* impl)
    {
        delete impl;
    }
}
```

### 3. Add to Build System
For premake5, add a new project to `premake5.lua`:
```lua
project_with_location("sensor_checker_my_sensor")
    group ("sensors/sensor_checker")
    language "C++"
    kind "SharedLib"
    staticruntime "Off"
    exceptionhandling "On"
    targetdir (package_dir.."/bin")
    cppdialect "C++17"

    defines { "SENSOR_CHECKER_EXPORTS" }

    includedirs
    {
        "%{root}/source/utilities/sensors/sensor_checker/include",
    }

    filter { "system:linux" }
        buildoptions { "-fPIC" }
        linkoptions { "-Wl,-rpath,'$$ORIGIN'" }
    filter { "system:windows" }
        disablewarnings { "4251" }
        rtti "On"
    filter {}

    files { "examples/MySensorCheckerImpl.cpp", "examples/MySensorCheckerImpl.h" }

    -- Set output name to match what SensorCheckerUtil expects
    targetname "SensorCheckerMySensorMyVendor"
```

### 4. Use the Implementation
```cpp
omni::sensors::ModelInfo modelInfo = {
    "MySensor",     // modelName
    "1.0",          // modelVersion
    "MyVendor",     // modelVendor
    "1.0",          // schemaVersion
    "default"       // marketName
};
checker.init(modelInfo);
// or with custom path
checker.init(modelInfo, "/path/to/libSensorCheckerMySensorMyVendor.so");
```

## Library Naming Convention

The system expects shared libraries to follow this naming pattern:
- **Linux**: `libSensorChecker{ModelName}{ModelVendor}.so`
- **Windows**: `SensorChecker{ModelName}{ModelVendor}.dll`

For example:
- `libSensorCheckerLidarCoreNVIDIA.so`

## Error Handling

The system gracefully handles:
- Missing libraries (prints error, just no implementation)
- Missing factory functions (unloads library and continues)
- Invalid implementations (calls are safely forwarded)
- Validation errors (returns descriptive error strings)

## Platform Support

- **Linux**: Uses `dlopen`, `dlsym`, `dlclose`
- **Windows**: Uses `LoadLibrary`, `GetProcAddress`, `FreeLibrary`
- **macOS**: Uses the same APIs as Linux


## Current Implementations

- **LidarCore**: NVIDIA's LiDAR sensor model validation
- **WPMDmatApprox** NVIDIA's WPM Radar sensor model validation
