public void Update()
        {
            var nodeStates        = new List <XRNodeState>();
            var unassignedDevices = new Queue <XRNodeState>();
            var serialNumbers     = new string[0];

            InputTracking.GetNodeStates(nodeStates);

            if (OpenVRActionManager.isRunning)
            {
                serialNumbers = OpenVRWrapper.GetTrackedDeviceSerialNumbers();
            }

            XRNodeState?headNodeState      = null;
            XRNodeState?leftHandNodeState  = null;
            XRNodeState?rightHandNodeState = null;
            XRNodeState?leftFootNodeState  = null;
            XRNodeState?rightFootNodeState = null;
            XRNodeState?waistNodeState     = null;

            int trackerCount = 0;

            foreach (XRNodeState nodeState in nodeStates)
            {
                if (!_foundDevices.Contains(nodeState.uniqueID))
                {
                    _foundDevices.Add(nodeState.uniqueID);
                    Plugin.logger.Debug($"Found new XR device of type \"{nodeState.nodeType}\" named \"{InputTracking.GetNodeName(nodeState.uniqueID)}\" with ID {nodeState.uniqueID}");
                }

                switch (nodeState.nodeType)
                {
                case XRNode.CenterEye:
                    headNodeState = nodeState;
                    break;

                case XRNode.LeftHand:
                    leftHandNodeState = nodeState;
                    break;

                case XRNode.RightHand:
                    rightHandNodeState = nodeState;
                    break;

                case XRNode.HardwareTracker:
                    if (OpenVRActionManager.isRunning)
                    {
                        // try to figure out tracker role using OpenVR
                        string deviceName     = InputTracking.GetNodeName(nodeState.uniqueID);
                        int    openVRDeviceId = Array.FindIndex(serialNumbers, s => !string.IsNullOrEmpty(s) && deviceName.Contains(s));
                        var    role           = TrackedDeviceType.Unknown;

                        if (openVRDeviceId != -1)
                        {
                            role = OpenVRWrapper.GetTrackedDeviceType((uint)openVRDeviceId);
                        }

                        switch (role)
                        {
                        case TrackedDeviceType.LeftFoot:
                            leftFootNodeState = nodeState;
                            break;

                        case TrackedDeviceType.RightFoot:
                            rightFootNodeState = nodeState;
                            break;

                        case TrackedDeviceType.Waist:
                            waistNodeState = nodeState;
                            break;

                        default:
                            unassignedDevices.Enqueue(nodeState);
                            break;
                        }
                    }
                    else
                    {
                        unassignedDevices.Enqueue(nodeState);
                    }

                    trackerCount++;

                    break;
                }
            }

            // fallback if OpenVR tracker roles aren't set/supported
            if (leftFootNodeState == null && trackerCount >= 2 && unassignedDevices.Count > 0)
            {
                leftFootNodeState = unassignedDevices.Dequeue();
            }

            if (rightFootNodeState == null && trackerCount >= 2 && unassignedDevices.Count > 0)
            {
                rightFootNodeState = unassignedDevices.Dequeue();
            }

            if (waistNodeState == null && unassignedDevices.Count > 0)
            {
                waistNodeState = unassignedDevices.Dequeue();
            }

            UpdateTrackedDevice(head, headNodeState, nameof(head));
            UpdateTrackedDevice(leftHand, leftHandNodeState, nameof(leftHand));
            UpdateTrackedDevice(rightHand, rightHandNodeState, nameof(rightHand));
            UpdateTrackedDevice(leftFoot, leftFootNodeState, nameof(leftFoot));
            UpdateTrackedDevice(rightFoot, rightFootNodeState, nameof(rightFoot));
            UpdateTrackedDevice(waist, waistNodeState, nameof(waist));

            foreach (ulong id in _foundDevices.ToList())
            {
                if (!nodeStates.Exists(n => n.uniqueID == id))
                {
                    Plugin.logger.Debug($"Lost XR device with ID " + id);
                    _foundDevices.Remove(id);
                }
            }
        }
        // ReSharper restore UnusedMember.Local
        #pragma warning restore IDE0051
        #endregion

        private void UpdateInputDevices()
        {
            var inputDevices                = new List <InputDevice>();
            var unassignedDevices           = new Queue <InputDevice>();
            var openVRDevicesBySerialNumber = new Dictionary <string, uint>();

            InputDevices.GetDevices(inputDevices);

            var deviceRoles = new Dictionary <string, TrackedDeviceRole>(inputDevices.Count);

            if (_isOpenVRRunning)
            {
                string[] serialNumbers = OpenVRWrapper.GetTrackedDeviceSerialNumbers();

                for (uint i = 0; i < serialNumbers.Length; i++)
                {
                    if (string.IsNullOrEmpty(serialNumbers[i]))
                    {
                        continue;
                    }

                    Plugin.logger.Debug($"Got serial number \"{serialNumbers[i]}\" for device at index {i}");
                    openVRDevicesBySerialNumber.Add(serialNumbers[i], i);
                }
            }

            InputDevice?headInputDevice      = null;
            InputDevice?leftHandInputDevice  = null;
            InputDevice?rightHandInputDevice = null;
            InputDevice?waistInputDevice     = null;
            InputDevice?leftFootInputDevice  = null;
            InputDevice?rightFootInputDevice = null;

            int trackerCount = 0;

            foreach (InputDevice device in inputDevices)
            {
                if (!device.isValid)
                {
                    continue;
                }

                deviceRoles.Add(device.name, TrackedDeviceRole.Unknown);

                if (!_foundDevices.Contains(device.name))
                {
                    Plugin.logger.Info($"Found new input device \"{device.name}\" with serial number \"{device.serialNumber}\"");
                    _foundDevices.Add(device.name);
                }

                if (device.HasCharacteristics(InputDeviceCharacteristics.HeadMounted))
                {
                    headInputDevice = device;
                }
                else if (device.HasCharacteristics(InputDeviceCharacteristics.HeldInHand |
                                                   InputDeviceCharacteristics.Left))
                {
                    leftHandInputDevice = device;
                }
                else if (device.HasCharacteristics(InputDeviceCharacteristics.HeldInHand |
                                                   InputDeviceCharacteristics.Right))
                {
                    rightHandInputDevice = device;
                }
                else if (device.HasCharacteristics(InputDeviceCharacteristics.TrackedDevice) && !device.HasCharacteristics(InputDeviceCharacteristics.TrackingReference))
                {
                    if (_isOpenVRRunning &&
                        !string.IsNullOrEmpty(device.serialNumber) &&
                        openVRDevicesBySerialNumber.TryGetValue(device.serialNumber, out uint openVRDeviceId))
                    {
                        // try to figure out tracker role using OpenVR
                        var role = OpenVRWrapper.GetTrackedDeviceRole(openVRDeviceId);
                        deviceRoles[device.name] = role;

                        Plugin.logger.Info($"Tracker \"{device.name}\" has role {role}");

                        switch (role)
                        {
                        case TrackedDeviceRole.Waist:
                            waistInputDevice = device;
                            break;

                        case TrackedDeviceRole.LeftFoot:
                            leftFootInputDevice = device;
                            break;

                        case TrackedDeviceRole.RightFoot:
                            rightFootInputDevice = device;
                            break;

                        default:
                            unassignedDevices.Enqueue(device);
                            break;
                        }
                    }
                    else
                    {
                        unassignedDevices.Enqueue(device);
                    }

                    trackerCount++;
                }
            }

            // fallback if OpenVR tracker roles aren't set/supported
            if (leftFootInputDevice == null && trackerCount >= 2 && unassignedDevices.Count > 0)
            {
                leftFootInputDevice = unassignedDevices.Dequeue();
            }

            if (rightFootInputDevice == null && trackerCount >= 2 && unassignedDevices.Count > 0)
            {
                rightFootInputDevice = unassignedDevices.Dequeue();
            }

            if (waistInputDevice == null && unassignedDevices.Count > 0)
            {
                waistInputDevice = unassignedDevices.Dequeue();
            }

            AssignTrackedDevice(head, headInputDevice, DeviceUse.Head, headInputDevice.HasValue      ? deviceRoles[headInputDevice.Value.name]      : TrackedDeviceRole.Unknown);
            AssignTrackedDevice(leftHand, leftHandInputDevice, DeviceUse.LeftHand, leftHandInputDevice.HasValue  ? deviceRoles[leftHandInputDevice.Value.name]  : TrackedDeviceRole.Unknown);
            AssignTrackedDevice(rightHand, rightHandInputDevice, DeviceUse.RightHand, rightHandInputDevice.HasValue ? deviceRoles[rightHandInputDevice.Value.name] : TrackedDeviceRole.Unknown);
            AssignTrackedDevice(waist, waistInputDevice, DeviceUse.Waist, waistInputDevice.HasValue     ? deviceRoles[waistInputDevice.Value.name]     : TrackedDeviceRole.Unknown);
            AssignTrackedDevice(leftFoot, leftFootInputDevice, DeviceUse.LeftFoot, leftFootInputDevice.HasValue  ? deviceRoles[leftFootInputDevice.Value.name]  : TrackedDeviceRole.Unknown);
            AssignTrackedDevice(rightFoot, rightFootInputDevice, DeviceUse.RightFoot, rightFootInputDevice.HasValue ? deviceRoles[rightFootInputDevice.Value.name] : TrackedDeviceRole.Unknown);

            foreach (string deviceName in _foundDevices.ToList())
            {
                if (!inputDevices.Exists(d => d.name == deviceName))
                {
                    Plugin.logger.Info($"Lost device \"{deviceName}\"");
                    _foundDevices.Remove(deviceName);
                }
            }
        }