Controller API - RiftCat/vridge-api GitHub Wiki

Table of Contents

Quick facts:

  • Works as REP ZMQ socket listening at port assigned by control service while VRidge is running.
  • All matrix use column-major byte layout.
  • Uses protobuf-serialized messages
  • Once you send controller state, it's used until you override it.
  • Haptic pulses are handled by broadcast channel based on PUB-SUB architecture - see here

Connecting

See Control channel page to find out how to connect to this endpoint.

Sending controller state

Controller protocol is stateless. Once you send controller state it will be there in the given state until you update it or mark it as disabled. Use different controllerIds to spawn different controllers.

Since VRidge 2.3.x controllers that weren't updated for 5 seconds will now be automatically grayed out and hidden.

Packet layout

Request packets

[ProtoContract]
public struct ControllerRequest 
{
    [ProtoMember(1)]
    public int Version = 3;

    // See ControllerTask below
    [ProtoMember(2)]
    public byte TaskType;

    // See VRController below
    [ProtoMember(4)]
    public VRController ControllerState;
}

Task and origin use constants found below.

public enum ControllerTask
{
    /// <summary>
    /// Packet closes your controller API connection and lets other clients use it.
    /// </summary>        
    Disconnect = 255,

    /// <summary>
    /// Packet contains full controller state as defined in VRController struct.
    /// It will be used immediately.
    /// </summary>
    SendFullState = 1,

    /// <summary>
    /// Recenter head tracking. Works the same as pressing recenter hotkey as configured in VRidge settings.
    /// </summary>
    RecenterHead = 2
}  
[ProtoContract]
public struct VRController
{
    // Multiple controllers need different unique Ids
    [ProtoMember(1)]
    public int ControllerId;

    // 0 == active, 1 == connected but not tracked, 2 == gone
    [ProtoMember(2)]
    public int Status;    

    // See further below
    [ProtoMember(4)]
    public VRControllerState_t ButtonState;

    // Currently ignored, may be used in the future
    [ProtoMember(5)]
    public double[] Acceleration;

    // float3 velocity vector
    [ProtoMember(6)]
    public double[] Velocity;

    // See further below
    [ProtoMember(7)]
    public HeadRelation HeadRelation;
    
    // Will be forwarded to SteamVr and apps to be used in-game.
    [ProtoMember(8)]
    public HandType SuggestedHand;    

    // Timestamp (seconds) of sensor data. 
    // It is used to derive velocity, if it is provided 
    [ProtoMember(9)]
    public double Timestamp;

    // Debug info only
    [ProtoMember(11)]
    public string Name;
    
    // XYZ vector.    
    [ProtoMember(12)]
    public float[] Position;
    
    // XYZW quaternion.    
    [ProtoMember(13)]
    public float[] Orientation;
}
/// <summary>
/// See VRControllerState_t in OpenVR docs. This is unchanged from OpenVR struct.
/// </summary>
[ProtoContract]
public struct VRControllerState_t
{
    [ProtoMember(1)]
    public uint unPacketNum;

    [ProtoMember(2)]
    public ulong ulButtonPressed;

    [ProtoMember(3)]
    public ulong ulButtonTouched;

    [ProtoMember(4)]
    public VRControllerAxis_t rAxis0;

    [ProtoMember(5)]
    public VRControllerAxis_t rAxis1;

    [ProtoMember(7)]
    public VRControllerAxis_t rAxis2;

    [ProtoMember(8)]
    public VRControllerAxis_t rAxis3;

    [ProtoMember(9)]
    public VRControllerAxis_t rAxis4;
}
    [ProtoContract]
    public struct VRControllerAxis_t
    {
        [ProtoMember(1)]
        public float x;

        [ProtoMember(2)]
        public float y;

        public VRControllerAxis_t(float x, float y)
        {
            this.x = x;
            this.y = y;
        }
    }
public enum HeadRelation : byte
    {
        /// <summary>
        /// Pose is unrelated to current head pose. 
        //// This should be the best choice for nearly all use-cases.
        /// </summary>
        Unrelated = 0,

        /// <summary>
        /// Pose already is in head space.
        /// Will be auto-adjusted when head is recentered.
        /// </summary>
        IsInHeadSpace = 1,

        /// <summary>
        /// Pose is unrelated but is to be remapped in a way that assumes that pose forward
        /// should always be aligned to head's forward. Effectively the given pose is relative angle from current's head forward.
        /// </summary>
        SticksToHead = 2
    }

Response packets:

Response is simpler.

[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct ControllerStateResponse
{        
    public int  Version;         
    public byte ReplyCode;       

    public enum Response
    {
        /// <summary>
        /// In response to disconnect request.
        /// </summary>
        Disconnecting = 255,

        /// <summary>
        /// When request was not understood
        /// </summary>
        BadRequest = 254,

        AcceptedYourData = 0,            
    }        
}   

You can find those files here.

Full example

See source in ControllerRemote.cs and test it out in Desktop Example.

⚠️ **GitHub.com Fallback** ⚠️