# Sauna Driver API Documentation

This document provides detailed information about the custom IMU interface provided by the Sauna OpenVR driver. It explains how applications can access IMU data, especially when optical tracking is lost.

## IMU Interface Overview

The Sauna driver provides a custom interface called `IVRIMUComponent` that allows applications to access raw IMU data from tracked devices. This interface is particularly useful when optical tracking is lost, as it provides continuous access to accelerometer and gyroscope data.

### Key Features

- Access to raw IMU data (accelerometer and gyroscope)
- Ability to get IMU data even when optical tracking is lost
- Simple interface with just a few methods
- Compatible with the OpenVR component system

### When to Use the IMU Interface

The IMU interface is useful in the following scenarios:

1. When developing applications that need to work in environments where optical tracking may be unreliable
2. When implementing custom tracking algorithms that can benefit from raw IMU data
3. When creating applications that need to detect specific movements or gestures
4. When implementing fallback tracking mechanisms for improved user experience

## API Reference

### IVRIMUComponent Interface

The `IVRIMUComponent` interface is defined in `sauna_device_driver.h` as follows:

```cpp
class IVRIMUComponent
{
public:
    /**
     * @brief Get the latest IMU sample
     * 
     * @param pSample Pointer to store the IMU sample
     * @return true if IMU data was available
     * @return false if no IMU data was available
     */
    virtual bool GetLatestIMUSample(vr::ImuSample_t *pSample) = 0;
    
    /**
     * @brief Check if IMU data is available
     * 
     * @return true if IMU data is available
     * @return false if no IMU data is available
     */
    virtual bool IsIMUDataAvailable() = 0;
    
    /**
     * @brief Get IMU data even when optical tracking is lost
     * 
     * @param pSample Pointer to store the IMU sample
     * @return true if IMU data was available
     * @return false if no IMU data was available
     */
    virtual bool GetIMUDataInFallbackMode(vr::ImuSample_t *pSample) = 0;
};
```

The interface version string is defined as:

```cpp
static const char *IVRIMUComponent_Version = "IVRIMUComponent_001";
```

### Methods

#### GetLatestIMUSample

```cpp
virtual bool GetLatestIMUSample(vr::ImuSample_t *pSample) = 0;
```

**Description:**  
Gets the latest IMU sample from the device.

**Parameters:**  
- `pSample`: Pointer to a `vr::ImuSample_t` structure where the IMU sample will be stored.

**Return Value:**  
- `true` if IMU data was available and the sample was successfully retrieved.
- `false` if no IMU data was available or an error occurred.

**Usage:**  
This method retrieves the latest IMU sample from the device, regardless of the tracking state. It's useful for applications that want to access raw IMU data for any purpose.

#### IsIMUDataAvailable

```cpp
virtual bool IsIMUDataAvailable() = 0;
```

**Description:**  
Checks if IMU data is available for the device.

**Parameters:**  
None

**Return Value:**  
- `true` if IMU data is available.
- `false` if no IMU data is available.

**Usage:**  
This method can be used to check if IMU data is available before attempting to retrieve it. It's a quick way to determine if the device supports IMU data and if there are any samples available.

#### GetIMUDataInFallbackMode

```cpp
virtual bool GetIMUDataInFallbackMode(vr::ImuSample_t *pSample) = 0;
```

**Description:**  
Gets IMU data specifically when the device is in fallback mode (optical tracking lost).

**Parameters:**  
- `pSample`: Pointer to a `vr::ImuSample_t` structure where the IMU sample will be stored.

**Return Value:**  
- `true` if the device is in fallback mode and IMU data was successfully retrieved.
- `false` if the device is not in fallback mode or no IMU data was available.

**Usage:**  
This method is specifically designed for applications that want to access IMU data only when optical tracking is lost. It's useful for implementing custom fallback tracking mechanisms.

### Error Handling

The IMU interface methods return boolean values to indicate success or failure. Applications should check these return values and handle errors appropriately. Common error scenarios include:

1. No IMU data available (device doesn't support IMU or no samples have been collected)
2. Device not in fallback mode (for `GetIMUDataInFallbackMode`)
3. Invalid pointer passed to the method

## Data Structures

### ImuSample_t

The `vr::ImuSample_t` structure is defined in the OpenVR API and contains the following fields:

```cpp
struct ImuSample_t
{
    double fSampleTime;
    HmdVector3d_t vAccel;
    HmdVector3d_t vGyro;
};
```

**Fields:**  
- `fSampleTime`: The time when the sample was taken, in seconds since the driver started.
- `vAccel`: The accelerometer data, in meters per second squared (m/s²).
- `vGyro`: The gyroscope data, in radians per second (rad/s).

### HmdVector3d_t

The `vr::HmdVector3d_t` structure is defined in the OpenVR API and contains the following fields:

```cpp
struct HmdVector3d_t
{
    double v[3];
};
```

**Fields:**  
- `v[0]`: The X component of the vector.
- `v[1]`: The Y component of the vector.
- `v[2]`: The Z component of the vector.

### Coordinate System

The IMU data uses the following coordinate system:

- X-axis: Points to the right when facing forward
- Y-axis: Points upward
- Z-axis: Points backward (opposite to the forward direction)

For accelerometer data:
- Positive X: Acceleration to the right
- Positive Y: Acceleration upward
- Positive Z: Acceleration backward

For gyroscope data:
- Positive X: Rotation around the X-axis (pitch)
- Positive Y: Rotation around the Y-axis (yaw)
- Positive Z: Rotation around the Z-axis (roll)

## Accessing the IMU Interface

To access the IMU interface from an application, you need to:

1. Get a pointer to the tracked device
2. Get the IMU component from the device
3. Use the IMU component to access IMU data

Here's how to do it:

```cpp
// Get the IMU component from a tracked device
IVRIMUComponent* GetIMUComponent(vr::TrackedDeviceIndex_t unDeviceIndex)
{
    vr::IVRSystem* pVRSystem = vr::VR_Init(nullptr, vr::VRApplication_Scene);
    if (!pVRSystem)
        return nullptr;

    // Get the property container for the device
    vr::PropertyContainerHandle_t container = pVRSystem->GetPropertyContainer(unDeviceIndex);
    if (container == vr::k_ulInvalidPropertyContainer)
        return nullptr;

    // Get the IMU component
    void* pComponent = pVRSystem->GetComponentForPropertyContainer(container, IVRIMUComponent_Version);
    return static_cast<IVRIMUComponent*>(pComponent);
}
```

## Code Examples

### Example 1: Basic IMU Data Access

This example shows how to access IMU data from a tracked device:

```cpp
#include <openvr.h>
#include "sauna_device_driver.h" // For IVRIMUComponent definition

void AccessIMUData()
{
    // Initialize OpenVR
    vr::EVRInitError eError = vr::VRInitError_None;
    vr::IVRSystem* pVRSystem = vr::VR_Init(&eError, vr::VRApplication_Scene);
    if (eError != vr::VRInitError_None)
    {
        printf("Failed to initialize OpenVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(eError));
        return;
    }

    // Get the HMD device index (usually 0)
    vr::TrackedDeviceIndex_t hmdIndex = vr::k_unTrackedDeviceIndex_Hmd;

    // Get the property container for the HMD
    vr::PropertyContainerHandle_t container = pVRSystem->GetPropertyContainer(hmdIndex);
    if (container == vr::k_ulInvalidPropertyContainer)
    {
        printf("Failed to get property container for HMD\n");
        vr::VR_Shutdown();
        return;
    }

    // Get the IMU component
    void* pComponent = pVRSystem->GetComponentForPropertyContainer(container, IVRIMUComponent_Version);
    IVRIMUComponent* pIMUComponent = static_cast<IVRIMUComponent*>(pComponent);
    if (!pIMUComponent)
    {
        printf("Failed to get IMU component\n");
        vr::VR_Shutdown();
        return;
    }

    // Check if IMU data is available
    if (pIMUComponent->IsIMUDataAvailable())
    {
        // Get the latest IMU sample
        vr::ImuSample_t sample;
        if (pIMUComponent->GetLatestIMUSample(&sample))
        {
            printf("IMU Sample:\n");
            printf("  Time: %f\n", sample.fSampleTime);
            printf("  Accel: [%f, %f, %f]\n", sample.vAccel.v[0], sample.vAccel.v[1], sample.vAccel.v[2]);
            printf("  Gyro: [%f, %f, %f]\n", sample.vGyro.v[0], sample.vGyro.v[1], sample.vGyro.v[2]);
        }
        else
        {
            printf("Failed to get IMU sample\n");
        }
    }
    else
    {
        printf("IMU data is not available\n");
    }

    // Shutdown OpenVR
    vr::VR_Shutdown();
}
```

### Example 2: Fallback Tracking Implementation

This example shows how to implement a simple fallback tracking mechanism using IMU data:

```cpp
#include <openvr.h>
#include "sauna_device_driver.h" // For IVRIMUComponent definition

// Simple quaternion class for rotation
class Quaternion
{
public:
    double w, x, y, z;

    Quaternion() : w(1.0), x(0.0), y(0.0), z(0.0) {}
    Quaternion(double w, double x, double y, double z) : w(w), x(x), y(y), z(z) {}

    // Normalize the quaternion
    void Normalize()
    {
        double magnitude = sqrt(w * w + x * x + y * y + z * z);
        if (magnitude > 0.0)
        {
            w /= magnitude;
            x /= magnitude;
            y /= magnitude;
            z /= magnitude;
        }
        else
        {
            w = 1.0;
            x = y = z = 0.0;
        }
    }

    // Multiply with another quaternion
    Quaternion operator*(const Quaternion& q) const
    {
        return Quaternion(
            w * q.w - x * q.x - y * q.y - z * q.z,
            w * q.x + x * q.w + y * q.z - z * q.y,
            w * q.y - x * q.z + y * q.w + z * q.x,
            w * q.z + x * q.y - y * q.x + z * q.w
        );
    }
};

// Create a quaternion from axis-angle representation
Quaternion QuaternionFromAxisAngle(const double axis[3], double angle)
{
    double halfAngle = angle * 0.5;
    double sinHalfAngle = sin(halfAngle);
    
    return Quaternion(
        cos(halfAngle),
        axis[0] * sinHalfAngle,
        axis[1] * sinHalfAngle,
        axis[2] * sinHalfAngle
    );
}

void ImplementFallbackTracking()
{
    // Initialize OpenVR
    vr::EVRInitError eError = vr::VRInitError_None;
    vr::IVRSystem* pVRSystem = vr::VR_Init(&eError, vr::VRApplication_Scene);
    if (eError != vr::VRInitError_None)
    {
        printf("Failed to initialize OpenVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(eError));
        return;
    }

    // Get the HMD device index (usually 0)
    vr::TrackedDeviceIndex_t hmdIndex = vr::k_unTrackedDeviceIndex_Hmd;

    // Get the property container for the HMD
    vr::PropertyContainerHandle_t container = pVRSystem->GetPropertyContainer(hmdIndex);
    if (container == vr::k_ulInvalidPropertyContainer)
    {
        printf("Failed to get property container for HMD\n");
        vr::VR_Shutdown();
        return;
    }

    // Get the IMU component
    void* pComponent = pVRSystem->GetComponentForPropertyContainer(container, IVRIMUComponent_Version);
    IVRIMUComponent* pIMUComponent = static_cast<IVRIMUComponent*>(pComponent);
    if (!pIMUComponent)
    {
        printf("Failed to get IMU component\n");
        vr::VR_Shutdown();
        return;
    }

    // Initialize tracking state
    Quaternion orientation(1.0, 0.0, 0.0, 0.0);
    double lastSampleTime = 0.0;
    bool hasLastSample = false;

    // Main loop
    while (true)
    {
        // Get the latest IMU sample
        vr::ImuSample_t sample;
        if (pIMUComponent->GetIMUDataInFallbackMode(&sample))
        {
            // We're in fallback mode and have IMU data
            if (hasLastSample)
            {
                // Calculate the time delta
                double dt = sample.fSampleTime - lastSampleTime;
                if (dt > 0.0)
                {
                    // Get the angular velocity from the gyroscope
                    double angularVelocity[3] = {
                        sample.vGyro.v[0],
                        sample.vGyro.v[1],
                        sample.vGyro.v[2]
                    };

                    // Calculate the angular velocity magnitude
                    double angularVelocityMagnitude = sqrt(
                        angularVelocity[0] * angularVelocity[0] +
                        angularVelocity[1] * angularVelocity[1] +
                        angularVelocity[2] * angularVelocity[2]
                    );

                    if (angularVelocityMagnitude > 0.0)
                    {
                        // Normalize the angular velocity to get the rotation axis
                        double rotationAxis[3] = {
                            angularVelocity[0] / angularVelocityMagnitude,
                            angularVelocity[1] / angularVelocityMagnitude,
                            angularVelocity[2] / angularVelocityMagnitude
                        };

                        // Calculate the rotation angle
                        double rotationAngle = angularVelocityMagnitude * dt;

                        // Create a quaternion from the axis-angle representation
                        Quaternion deltaRotation = QuaternionFromAxisAngle(rotationAxis, rotationAngle);

                        // Apply the rotation to the current orientation
                        orientation = orientation * deltaRotation;
                        orientation.Normalize();

                        // Use the updated orientation for rendering
                        printf("Orientation: [%f, %f, %f, %f]\n", orientation.w, orientation.x, orientation.y, orientation.z);
                    }
                }
            }

            // Store the sample for the next iteration
            lastSampleTime = sample.fSampleTime;
            hasLastSample = true;
        }
        else
        {
            // We're not in fallback mode or no IMU data is available
            // Use the normal tracking from OpenVR
            vr::TrackedDevicePose_t pose;
            pVRSystem->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, 0.0f, &pose, 1);

            if (pose.bPoseIsValid)
            {
                // Extract the orientation from the pose matrix
                vr::HmdQuaternion_t q = vr::HmdQuaternion_FromMatrix(pose.mDeviceToAbsoluteTracking);
                orientation = Quaternion(q.w, q.x, q.y, q.z);
                printf("Tracking OK: [%f, %f, %f, %f]\n", orientation.w, orientation.x, orientation.y, orientation.z);
            }
        }

        // Sleep for a short time
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    // Shutdown OpenVR
    vr::VR_Shutdown();
}
```

### Example 3: Accessing IMU Data for Multiple Devices

This example shows how to access IMU data for all tracked devices:

```cpp
#include <openvr.h>
#include "sauna_device_driver.h" // For IVRIMUComponent definition

void AccessAllDevicesIMUData()
{
    // Initialize OpenVR
    vr::EVRInitError eError = vr::VRInitError_None;
    vr::IVRSystem* pVRSystem = vr::VR_Init(&eError, vr::VRApplication_Scene);
    if (eError != vr::VRInitError_None)
    {
        printf("Failed to initialize OpenVR: %s\n", vr::VR_GetVRInitErrorAsEnglishDescription(eError));
        return;
    }

    // Iterate through all possible tracked devices
    for (vr::TrackedDeviceIndex_t unDeviceIndex = 0; unDeviceIndex < vr::k_unMaxTrackedDeviceCount; unDeviceIndex++)
    {
        // Check if the device is connected
        if (!pVRSystem->IsTrackedDeviceConnected(unDeviceIndex))
            continue;

        // Get the device class
        vr::ETrackedDeviceClass deviceClass = pVRSystem->GetTrackedDeviceClass(unDeviceIndex);
        const char* deviceClassName = "Unknown";
        switch (deviceClass)
        {
        case vr::TrackedDeviceClass_HMD:
            deviceClassName = "HMD";
            break;
        case vr::TrackedDeviceClass_Controller:
            deviceClassName = "Controller";
            break;
        case vr::TrackedDeviceClass_GenericTracker:
            deviceClassName = "Tracker";
            break;
        case vr::TrackedDeviceClass_TrackingReference:
            deviceClassName = "Base Station";
            break;
        }

        printf("Device %u: %s\n", unDeviceIndex, deviceClassName);

        // Get the property container for the device
        vr::PropertyContainerHandle_t container = pVRSystem->GetPropertyContainer(unDeviceIndex);
        if (container == vr::k_ulInvalidPropertyContainer)
        {
            printf("  Failed to get property container\n");
            continue;
        }

        // Get the IMU component
        void* pComponent = pVRSystem->GetComponentForPropertyContainer(container, IVRIMUComponent_Version);
        IVRIMUComponent* pIMUComponent = static_cast<IVRIMUComponent*>(pComponent);
        if (!pIMUComponent)
        {
            printf("  No IMU component available\n");
            continue;
        }

        // Check if IMU data is available
        if (pIMUComponent->IsIMUDataAvailable())
        {
            // Get the latest IMU sample
            vr::ImuSample_t sample;
            if (pIMUComponent->GetLatestIMUSample(&sample))
            {
                printf("  IMU Sample:\n");
                printf("    Time: %f\n", sample.fSampleTime);
                printf("    Accel: [%f, %f, %f]\n", sample.vAccel.v[0], sample.vAccel.v[1], sample.vAccel.v[2]);
                printf("    Gyro: [%f, %f, %f]\n", sample.vGyro.v[0], sample.vGyro.v[1], sample.vGyro.v[2]);
            }
            else
            {
                printf("  Failed to get IMU sample\n");
            }
        }
        else
        {
            printf("  IMU data is not available\n");
        }
    }

    // Shutdown OpenVR
    vr::VR_Shutdown();
}
```

## Best Practices

When using the IMU interface, follow these best practices:

1. **Check for Availability**: Always check if IMU data is available before trying to access it.

2. **Handle Errors**: Check the return values of all methods and handle errors appropriately.

3. **Use the Right Method**: Use `GetIMUDataInFallbackMode` when you specifically want to access IMU data during optical tracking loss, and `GetLatestIMUSample` when you want to access IMU data regardless of tracking state.

4. **Filter the Data**: Raw IMU data can be noisy. Consider applying a low-pass filter or other filtering techniques to smooth the data.

5. **Calibrate the IMU**: IMU sensors may have biases and scaling factors. Consider implementing calibration procedures for more accurate results.

6. **Respect Performance**: Accessing IMU data too frequently can impact performance. Consider the update rate of your application and the IMU data.

7. **Combine with Optical Tracking**: For best results, combine IMU data with optical tracking data when available. Use IMU data as a fallback when optical tracking is lost.