2 Arduino's Leonardo as joystick inputs, only one inputs it's axis.

For a vehicle based project we are using 2 Arduino’s. They are set up using a joystick library and get their inputs in Windows are fine.
When using on or the other the inputs in Unity are also correct but when both are plugged in some of the axis inputs are broken. The Stick/x and Stick/y inputs are fine, but the Z, Rx, Ry and Rz of only one of the arduino’s gets read into the new input system.
The arduino’s are setup so that there is no overlap between these axis, Rx and Ry are for pedals on one board while Rz is used for the steering wheel on the other.

When reading the raw values from the Joystick component I can see that the input of one board is set to 0 for all axis.

        for (int i = 0; i < Joystick.all.Count; i++)
        {
            Debug.Log($"stick {i}: " + Joystick.all[i].name);
            Debug.Log("Z :" + Joystick.all[i].allControls.Where((x) => x.displayName == "Z").FirstOrDefault().ReadValueAsObject().ToString());
            Debug.Log("Rx :" + Joystick.all[i].allControls.Where((x) => x.displayName == "Rx").FirstOrDefault().ReadValueAsObject().ToString());
            Debug.Log("Ry :" + Joystick.all[i].allControls.Where((x) => x.displayName == "Ry").FirstOrDefault().ReadValueAsObject().ToString());
            Debug.Log("Rz :" + Joystick.all[i].allControls.Where((x) => x.displayName == "Rz").FirstOrDefault().ReadValueAsObject().ToString());
        }

When I disable and re-enable both the Joysticks the raw input seems fine, yet the input manager doesn’t seem to get the correct values.

I was thinking that this might be something to do with Joystick.current, that one of them doesn’t get said to current when the axis value is changed, but using MakeCurrent() doesn’t seem to be doing much for them. The weirdest part to me is that all the button inputs work fine regardless of what arduino is the current one.

I’m currently on Unity 2020.3.8f1 and input system version 1.0.2

Try upgrading to input system 1.1.1 and see if this changes anything (probably will need to update manifest.json line in your project to 1.1.1)

Also in input debugger you can double click on a device and then double click on events in the trace to see raw bytes, are they valid for both devices?

Just tried updating to 1.1.1, and sadly nothing changed.


These are the values I get from de debug view, the rz on the left is the steering wheel is getting correct input, the rx, ry and z on the right are also getting their correct inputs here. But when asked from the input manager the values are wrong.

But when asked from the input manager the values are wrong.

You mean UnityEngine.Input? Or how do you ask for the values?

I’m using an input action asset and reading the value in script during FixedUpdate().

float arduinoGas = InputManager.instance.Controls.TruckInteractions.GasArduino.ReadValue<float>();

7548322--932917--upload_2021-10-5_16-10-2.png

7548322--932917--upload_2021-10-5_16-10-2.png

Try reading from normal update and check that input system update setting set to dynamic.
We might have a bag of other issues in FixedUpdate so it would be good to bisect this first.

Your input asset screenshot looks like you’re binding to Ry, but that only binds to one axis, what is your expectation there? To bind to two axis and threat them as one?

Update was already set to Process Events in Dynamic update.
I get the same issue in Update. Also tried it in a clean project, a build and a build on another computer. The problem persist throughout.

Original project:

New project:

Code I used to print in original project:

    private void Update()
    {
        Debug.Log($"gas/Ry: {InputManager.instance.Controls.TruckInteractions.GasArduino.ReadValue<float>()}");
        Debug.Log($"break/Rx: {InputManager.instance.Controls.TruckInteractions.BreakArduino.ReadValue<float>()}");
        Debug.Log($"MastLift/Z: {InputManager.instance.Controls.TruckInteractions.MastLiftArduino.ReadValue<float>()}");
        Debug.Log($"Steering/Rz: {InputManager.instance.Controls.TruckInteractions.SteeringArduino.ReadValue<float>()}");
    }

Maybe I’ve used the wrong terminology here, sorry if I did.
The idea here is that the Ry is used for a gas pedal that has a range from -1 to 1.
Rz is the steering wheel, Rx is the break and Z is a minilever that moves back and forth.
The Joystick x and y axis are 2 more minilevers, but they seem to be working fine all the time.

I tried changing the Arduino to a Gamepad instead of a Joystick hoping that would change anything.
But the autogenerated HID doesn’t match the HID that gets generated in the Arduino INO file.
7550587--933373--upload_2021-10-6_11-52-32.png


It’s not picking up as a gamepad in Unity and I also disabled the Hat switches yet they still show up in the input Debugger.

EDIT: I looked in the HID.cs code and it seems like the arduino showing op as a Joystick is intended behaviour. If I print all the controls from the Joystick object it also seens that it does not have the hatswitches in there anymore.

If I understand correctly, you have two separate devices with same layout, and you also have one action with one binding, it obviously getting resolved to only one of the devices. To support two devices bound to same action you need an action with two separate bindings.

Could be wrong regarding what is the problem here though

I don’t fully understand your answer here. I have two Arduino Leonardo’s that are setup as joysticks(gamepads). Their layout is indeed the same, but their buttons and axis don’t overlap. So the steering arduino has the steering wheel on Rz and the other board doesn’t set any data to Rz.

 for (int i = 0; i < Joystick.all.Count; i++)
            {
                // Look if it's the pedal/minilever arduino
                float? miniLever = Joystick.all[i].allControls.Where((x) => x.displayName == "Button 32").FirstOrDefault()?.ReadValueAsObject() as float?;
                Debug.Log(miniLever);
                if (miniLever.HasValue && miniLever.Value == 1)
                {
                    Rx = Joystick.all[i].allControls.Where((x) => x.displayName == "Rx").FirstOrDefault() as AxisControl;
                    Ry = Joystick.all[i].allControls.Where((x) => x.displayName == "Ry").FirstOrDefault() as AxisControl;
                    Z = Joystick.all[i].allControls.Where((x) => x.displayName == "Z").FirstOrDefault() as AxisControl;

                    setup = true;
                }
                else
                {
                    Rz = Joystick.all[i].allControls.Where((x) => x.displayName == "Rz").FirstOrDefault() as AxisControl;
                }
            }

            if (setup)
            {
                if (Ry != null)
                    Controls.Arduion.TestThrottle.AddBinding(Ry);
                if (Z != null)
                    Controls.Arduion.TestZ.AddBinding(Z);
                if (Rx != null)
                    Controls.Arduion.TestZ.AddBinding(Rx);
                if (Rz != null)
                    Controls.Arduion.TestRx.AddBinding(Rz);
            }

I’ve tried to setup the binding in code, I set an unused button on one of the boards so I know with what board I’m dealing. But this doesn’t seem to be solving it either.

7553281--933895--upload_2021-10-7_9-13-27.png

If I want to add a binding I have only one Arduino to add a binding off. Do I need to add the same binding twice then?

Ok, I assume device support works fine from what you’re writing as input system sees two separate device instances and the corresponding input controls seem to work as intendent in input debugger.

What you’re probably hitting is when you add an action with a binding, we resolve a binding to a device, not a control.
So in this case all your bindings resolve to first device in the chain, and second is ignored? It’s like binding to “/leftStick” and plugging in two gamepads, the input system will only bind to first gamepad and input from second will be only activated if second gamepad somehow becomes current, e.g. a button was pressed or something else.

I think the solution here is to use wildcard bindings, e.g. to bind on Rz on all devices at a same time, you should be able to achieve this by changing your binding to “*/Rz” and then in runtime in input debugger you should see the action being bound to both devices at a same time

First of all, thank you for all the help already. It’s been quite the week getting this to work.

7553983--933961--upload_2021-10-7_14-3-57.png
You mean like this? It was already doing this before.

It might be depending on wich controller is current, but then I wonder why the X and Y axis are still being registered even if that controller isn’t the current one.

Using wildcards doesn’t seem to do anything diffrently.
7553983--933967--upload_2021-10-7_14-5-16.png

I found a solution that partialy works for me, but it’s without using the new input system and breaks part of the UI interactions because of that.

I get the the controllers in Update because the first few frames the values aren’t correct yet, once I get the valid values in I use properties to get the values when I need them.

for (int i = 0; i < Joystick.all.Count; i++)
  private void Update()
    {
         if (!_setup)
        {
            {
                // Look if it's the pedal/minilever arduino
                float? miniLever = Joystick.all[i].allControls.Where((x) => x.displayName == "Button 32").FirstOrDefault()?.ReadValueAsObject() as float?;
                //Debug.Log(miniLever);

                // Setup correct Axis
                if (miniLever.HasValue && miniLever.Value == 1)
                {
                    Rx = Joystick.all[i].allControls.Where((x) => x.displayName == "Rx").FirstOrDefault() as AxisControl;
                    Ry = Joystick.all[i].allControls.Where((x) => x.displayName == "Ry").FirstOrDefault() as AxisControl;
                    Z = Joystick.all[i].allControls.Where((x) => x.displayName == "Z").FirstOrDefault() as AxisControl;

                    _setup = true;
                }
                else
                {
                    Rz = Joystick.all[i].allControls.Where((x) => x.displayName == "Rz").FirstOrDefault() as AxisControl;
                }
            }
  }
 AxisControl Rx;
    AxisControl Ry;
    AxisControl Rz;
    AxisControl Z;

    public float Brake
    {
        get
        {
            if (_setup)
                return Rx.ReadValue();
            else
                return -1;
        }
    }

    public float Gas
    {
        get
        {
            if (_setup)
                return Ry.ReadValue();
            else
                return 1;
        }
    }

    public float Lift
    {
        get
        {
            if (_setup && !DisableLift)
                return Z.ReadValue();
            else
                return 0;
        }
    }

    public bool DisableLift = false;

    public float Steering
    {
        get
        {
            if (_setup)
                return Rz.ReadValue();
            else
                return 0;
        }
    }

Tbh I’m out of ideas and can’t really help further without being able to debug it :frowning:

My only idea so far is that somehow one device overtakes another one and either action binding resolution takes precedence over one, or action value conflict resolution ignores data from one or another. You can try debugging it yourself by placing breakpoints in ProcessControlStateChange and ShouldIgnoreControlStateChange and seeing if first of all, does data from both devices get there, and if yes, why it decides to ignore one.

Another approach could be to avoid the problem in a first place, from my understanding what you have is two devices that look the same from VID/PID perspective, but really are two independent types of input (wheel and pedals). You can just assign a usage type to both (so one is {Wheel} and another is {Pedals}) and make input binding include the usage, that way you can isolate inputs before they even get to actions, that should be more sound design over all.

How would you go about doing that?

Listen to devices (onDeviceChange), identify which one is which, use InputSystem.SetDeviceUsage to set usages.
Then if you press “T” in asset editor you can manually type binding as a string to something like “{MyUsage}/Rz”

Usages are custom tags on devices, so that way you can separate them.


This seemed to have worked like a charm in editor, thank you!

My only issue now is how can I be certain wich controller is wich? Is there a constant in the data on wich I can check?
For the moment I check the DeviceID, but this seems to changed when the controllers gets plugged in again.

The documentation on this states:
Remarks
This is only assigned once a device has been added to the system. No two devices will receive the same ID and no device will receive an ID that another device used before even if the device was removed. The only exception to this is if a device gets re-created as part of a layout change. For example, if a new layout is registered that replaces the Mouse layout, all Mouse devices will get recreated but will keep their existing device IDs.

IDs are assigned by the input runtime.

So for this I check the unused button again from my previous posts and that seems to be able to identify wich controller is wich.

After further testing this doesn’t seem to work in build.


On start the device is not there. When losing and then getting back into focus the game picks up the controllers but on the Enabled state the Value of the button is for both controllers 0.

Hm, device id is just an ever-increasing number for every device connected.

Now I wonder now what is easier (sorry :smile:) because to correctly identify a device it seems like you either need to listen data from device and see if pedal is pressed or not, or separate them by USB PID, or add an extra bit somewhere (in HID descriptor maybe?). You can achieve this by listening to device state changes, and if Rz is changed than assign “pedal” usage to it and so on. That way you will postpone binding resolution until devices are used which might and might not be problematic

Though on another hand figuring out why two bindings on same action didn’t work feels like kinda same amount of work.

I think I’ve got it to a somewhat stable now. I do need to tab in and out of the application for it to work though.
When I just start the app it looks like this:


Once I retabbed you can see OnDeviceChange worked:

I had to add a small delay because on Enabled the correct input wasn’t there yet.

    private void InputSystem_onDeviceChange(InputDevice arg1, InputDeviceChange arg2)
    {
        Debug.Log($"DeviceName: {arg1.displayName}; DeviceId: {arg1.deviceId}; InputChange: {arg2};");
        OnDeviceChange.text += $"DeviceName: {arg1.displayName}; DeviceId: {arg1.deviceId}; InputChange: {arg2};";

        if (arg1.displayName == "Arduino Leonardo" && (arg2 == InputDeviceChange.Added || arg2 == InputDeviceChange.Enabled))
        {
            StartCoroutine(WaitForSettingDevice(arg1));
        }
        OnDeviceChange.text += "\n";
    }

    private IEnumerator WaitForSettingDevice(InputDevice arg1)
    {
        yield return new WaitForSeconds(0.25f);

        float? btnValue = arg1.allControls.Where((x) => x.displayName == "Button 32").FirstOrDefault()?.ReadValueAsObject() as float?;
        if (btnValue.HasValue)
        {
            Debug.Log($"Value: {btnValue.Value}");
            OnDeviceChange.text += $"Value: {btnValue.Value};";
            if (btnValue.Value == 1)
            {
                InputSystem.SetDeviceUsage(arg1, "Pedals");
            }
            else
            {
                InputSystem.SetDeviceUsage(arg1, "Wheel");
            }
        }
    }

To get it working from the moment the application opens I set them on Awake. Looping over all the connected Joysticks.

        foreach(Joystick j in Joystick.all)
        {
            StartCoroutine(WaitForSettingDevice(j));
        }

Chiming in here because this has been the bane of my existence this week…

Using USB PID is non-viable for things like Flight Sim setups where you may have 2 or more identical devices that need to be consistently mapped to actions and is impractical to do on App Start because Unity decided to enumerate the devices in whatever fucking order it wanted to instead of respecting the Windows DirectInput priority list. The correct way to do this either respect the priority list or enumerate by USB physical Port ID.

1 Like

@Fenrisul our backend on Windows for HID stuff is RawInput, not DirecInput, so enumeration is a bit tricky. And not all devices give us something meaningful to identify them, e.g. if you plug two gamepads of the same model, unless we get a serial number, there is not much to differentiate them next time app is running.

Do you have suggestions what we should do here instead? I understand this is a tricky configuration situation for sims in general