T. Rudder Custom HID Input, Axis Bug

Continuing the discussion from Thrustmaster T.Rudder Not Found On Unity's Input System Package:

T.Rudder Project

Hi, we managed to make a Custom HID input using Input System 1.3.0 for ThrustMaster T.Rudder Pedal. The problem is, because all the inputs on the pedals are connected, we cannot seem to create an equal float output for all the axis input.

There are a total of three axis input that we can get from the pedal, such as:

  1. Left Toe Brake
  2. Rudder
  3. Right Toe Brake

The Left and Right Toe Brake is the same input as any pedal where you just press it and it we can get its float. Where the Rudder is the unique one because it is an input where we can move (not press) up and down the Left and Right Toe Brake where if the Right Toe Brake is at bottom and the Left Toe Brake is at the top (' ,) it will have a float value of 1 and vice versa. Now here’s where all the problem are:

  1. When we press either the Left or Right Toe Brake till the limit and get the float value in Unity, it will continue to update the float value up to three times. Basically, even if the value is already at 1 (Max Value), if we press it down again it will go back to -1 (Min Value) and it will continue to do those three more times.
  2. We cannot seem to find a script to get the Rudder axis and tried to change the offset to 2 (Because it uses a Z axis instead of X and Y) and the InputControl name to “rudder/z”, but all it did is making all the other inputs unrecognizable. [InputControl(name = "rudder/z", offset = 2, format = "VC2B", parameter = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    **EDIT: We managed to find the rudder input, which actually came from the Left Toe Brake X Input and Right Toe Brake Y Input, but when tried to get the float value, it reacted the same way as mentioned in problem number 1. The value it is getting is also not of an axis value, instead it is a button value. Because of that, when pressed, the value it will emit is only 1, 0 and -1. It will not have a decimal number at all (0.2, 0.16, 0.5, etc).

Here’s the full script for the IInputStateTypeInfo:

using System.Runtime.InteropServices;

using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;

[StructLayout(LayoutKind.Explicit, Size = 32)]
struct TRudderHIDInputReport : IInputStateTypeInfo
{
    // Because all HID input reports are tagged with the 'HID ' FourCC,
    // this is the format we need to use for this state struct.
    public FourCC format => new FourCC('H', 'I', 'D');

    // HID input reports can start with an 8-bit report ID. It depends on the device
    // whether this is present or not. On the PS4 DualShock controller, it is
    // present. We don't really need to add the field, but let's do so for the sake of
    // completeness. This can also help with debugging.
    [FieldOffset(0)] public byte reportId;

    // The InputControl annotations here probably look a little scary, but what we do
    // here is relatively straightforward. The fields we add we annotate with
    // [FieldOffset] to force them to the right location, and then we add InputControl
    // to attach controls to the fields. Each InputControl attribute can only do one of
    // two things: either it adds a new control or it modifies an existing control.
    // Given that our layout is based on Gamepad, almost all the controls here are
    // inherited from Gamepad, and we just modify settings on them.
    [InputControl(name = "rightToeBrake", layout = "Stick", format = "VC2B")]
    [InputControl(name = "rightToeBrake/x", offset = 0, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    [InputControl(name = "rightToeBrake/left", offset = 0, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert=false")]
    [InputControl(name = "rightToeBrake/right", offset = 0, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert")]
    [FieldOffset(1)] public byte rightToeBrakeX;

    [InputControl(name = "leftToeBrake", layout = "Stick", format = "VC2B")]
    [InputControl(name = "leftToeBrake/y", offset = 1, format = "BYTE",
        parameters = "invert,normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    [InputControl(name = "leftToeBrake/up", offset = 1, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert=false")]
    [InputControl(name = "leftToeBrake/down", offset = 1, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert")]
    [FieldOffset(2)] public byte leftToeBrakeY;

    //If we use the rudder variable, it will make both the Left and Right Toe unable to be detected by Unity
    [InputControl(name = "rudder", layout = "Stick", format = "VC2B")]
    [InputControl(name = "rudder/z", offset = 2, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5")]
    [InputControl(name = "rudder/forward", offset = 2, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0,clampMax=0.5,invert")]
    [InputControl(name = "rudder/backward", offset = 2, format = "BYTE",
        parameters = "normalize,normalizeMin=0,normalizeMax=1,normalizeZero=0.5,clamp,clampMin=0.5,clampMax=1,invert=false")]
    [FieldOffset(3)] public byte rudderZ;
}

And here’s the full script for the getting the float value in game:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CheckControl : MonoBehaviour
{
    [SerializeField] private InputActionAsset inputActionAsset;
    private InputActionMap tRudder;
    private InputActionMap t16000;
    private InputAction leftToeBrake;
    private InputAction rightToeBrake;
    private InputAction t16000lever;

    [SerializeField] private float leftToe;
    [SerializeField] private float rightToe;
    [SerializeField] private Vector2 leverInput;

    private void Start()
    {

        t16000 = inputActionAsset.FindActionMap("T.16000M");
        tRudder = inputActionAsset.FindActionMap("TRudder");
        leftToeBrake = tRudder.FindAction("Left Toe Brake");
        rightToeBrake = tRudder.FindAction("Right Toe Brake");
        t16000lever = t16000.FindAction("Lever");

        leftToeBrake.Enable();
        rightToeBrake.Enable();
        t16000lever.Enable();
    }

    private void Update()
    {
        leftToe = leftToeBrake.ReadValue<float>();
        rightToe = rightToeBrake.ReadValue<float>();
        leverInput = t16000lever.ReadValue<Vector2>();
    }
}

The reason we are trying to create the Custom HID Input for the T. Rudder Pedal, is because it is listed in the Input Debugger as one of the Unsupported Devices.

1 Like

Hello,

I’ve conversed with our developer and this is what they got back to me regarding this issue:

Based on this video, if the Windows Thrustmaster driver is installed then this device should report 3 axis controls: X=rightToe, Y=leftToe, Z=rudder.
It’s difficult to know what the device reports via HID without having the device myself, but I would imagine the layout would be something like:

[InputControl(layout = "Axis")] 
public float rightToeBrake; 

[InputControl(layout = "Axis")] 
public float leftToeBrake; 

[InputControl(layout = "Axis")] 
public float rudder;

Notice I used Axis rather than Stick
Also I have removed the format and other attributes as there are good defaults for those and need only specified if needed (here format will default to float format="FLT"
Formats are listed here
It looks like they copy-pasted from our docs , on that same page it mentions using the InputDebugger in Unity to try and understand what the device is reporting, they should try that if it somehow the device is using a different layout.

I hope this helps you, if you have any questions feel free to ask!

Hello! First of all, I have to say that your code was an absolute God-send for me since I was experiencing the EXACT SAME ISSUE and I simply just did not know where to even start. I downloaded your GitLab project and popped it into my own project and was able to fiddle with it until I got everything to work. Here’s the main points:

  1. If all you want is the rudder “z” axis, you don’t need to worry about the toebrake. All you have to do is change your “TRudderHIDInputReport.cs” code to:
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;

[StructLayout(LayoutKind.Explicit, Size = 32)]
struct TRudderHIDInputReport : IInputStateTypeInfo
{
    public FourCC format => new FourCC('H', 'I', 'D');

    [InputControl(name = "rudder", layout = "Stick", format = "VC2B")]
    [InputControl(name = "rudder/z", offset = 5, format = "BYTE",
        parameters = "normalize,normalizeMin=0.54,normalizeMax=0.78,normalizeZero=0.54", layout = "Axis")]
    [FieldOffset(3)] public byte rudderZ;
}

Basically, after playing with the original “offset” value that you had set to “3”, I discovered “offset = 5” actually provided a much more accurate reading of the “T.Rudder”/“TFRP” Thrustmaster Rudder Pedal position (however, “FieldOffset()” must stay at “3”). I also adjusted the “Normalize” values to center the input on “0”, with left-inputs reading as “-1” and right-inputs reading as “1” (I’m not quite sure why setting “normalize,normalizeMin=0.54,normalizeMax=0.78,normalizeZero=0.54” produces this effect, but it does). If you go to the Input Debugger by going to Window > Analysis > Input Debugger, you can select the “Devices” dropdown, and then double-click “TRudderJoystickHID” to bring up its input debugger window. You can then verify that “z” is a new input under “rudder”, and that the values change as you move the pedals left and right.

  1. In your “CheckControl.cs” file, you never enabled your InputAction variables, which is why you were never reading any input from your toebrakes. If you wanted to fix this, you can merely add “leftToeBrake.Enable()” after “leftToeBrake = tRudder.FindAction(“Left Toe Brake”)” and “rightToeBrake.Enable()” after “rightToeBrake = tRudder.FindAction(“Right Toe Brake”)”. However, I was only interested in the Rudder input alone. As a result, I tweaked your code to allow for the use of the “Rudder Z” custom input:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class CheckControl : MonoBehaviour
{
    [SerializeField] private InputActionAsset inputActionAsset;
    private InputActionMap tRudder;
    private InputAction rudderZ;

    [SerializeField] private float rudderZOutput;

    private void Start()
    {
        tRudder = inputActionAsset.FindActionMap("TRudder");
        rudderZ = tRudder.FindAction("Rudder Z");
        rudderZ.Enable();
    }

    private void OnDisable()
    {
        rudderZ.Disable();
    }

    private void Update()
    {
        rudderZOutput = rudderZ.ReadValue<float>();
    }
}
  1. Now with the Thrustmaster TFRP pedals available for use in Unity, there was one final thing to do. In the “Input Actions Editor GUI” I created a new action called “Rudder Z” (as you can see above, I reference it with “rudderZ = tRudder.FindAction(“Rudder Z”)”). I then added a binding underneath it, clicked the “T” icon for the “Path”, and pasted “<TRudderJoystickHID>/rudder/z”. Next, under the Input Actions Editor “Binding Properties” “Processors” section, click the “+” icon to add a new Processor and add an “Axis Deadzone” to the “Rudder Z” input binding (by default, the TFRP pedals ‘zero’ has some input noise wobble). Please note, that if you try to use the Binding path “Listen” button, the custom “/rudder/z” input will NOT appear. You MUST input the path using the binding “T” icon and enter the exact path as “<TRudderJoystickHID>/rudder/z” (or whatever you decided to name your custom input). A picture of my Input Actions Editor window with all my changes:

If you run your program with these adjustments, you should see the “Rudder Z” serialized variable’s float value adjust roughly between -1 and 1 in the inspector under whatever object contains the “CheckControl.cs” script. If any of the numbers seem strange, you can compensate for them by adjusting the “Normalize” “Min” and “Max” values under the Rudder Z binding in the Input Actions Editor GUI.

I’d be remiss if I didn’t mention that this is completely unnecessary if you use the “TFRP” pedals as a part of the T. Flight Thrustmaster Full Kit X HOTAS setup. In that configuration, the TFRP “RJ12” connector plugs straight into the stick portion of the HOTAS instead of a dedicated dongle, and Unity is able to read the pedal as a “Slider” input from the Thrustmaster Full Kit X controller with no issue. Also note that this ONLY works on Windows… I’m still not sure how to get the same HID process to work on macOS as the HID values appear to be completely random and sporadic there and I’m not quite sure what the root cause of that is.

Again, I can’t thank you enough for producing 99% of the work to get me to this point. You’re truly a life-saver.