diff --git a/Android/app/src/main/java/com/example/androidsteering/Connection.java b/Android/app/src/main/java/com/example/androidsteering/Connection.java index 32a7fc0..676a480 100644 --- a/Android/app/src/main/java/com/example/androidsteering/Connection.java +++ b/Android/app/src/main/java/com/example/androidsteering/Connection.java @@ -37,7 +37,7 @@ public class Connection { static class MyBuffer { - private final int MAX_SIZE = 20; + private final int MAX_SIZE = 10; private final ArrayList buff = new ArrayList<>(); private boolean running = false; private boolean updatePitch = true; @@ -49,7 +49,7 @@ public synchronized void addData(float pitch, float roll) if(updatePitch) buff.add(new Motion.MyMove(false, 0, pitch)); if(updateRoll) buff.add(new Motion.MyMove(false, 1, roll)); int idx = 0; - while(buff.size() > MAX_SIZE) + while(buff.size() > MAX_SIZE && idx < buff.size()) { if(buff.get(idx).MotionButton) { @@ -65,7 +65,7 @@ public synchronized void addData(int status, float val) if(!running) return; buff.add(new Motion.MyMove(false, status, val)); int idx = 0; - while(buff.size() > MAX_SIZE) + while(buff.size() > MAX_SIZE && idx < buff.size()) { if(buff.get(idx).MotionButton) { @@ -81,7 +81,7 @@ public synchronized void addData(MotionButton button) if(!running) return; buff.add(new Motion.MyMove(true, button.getVal(), 0.0f)); int idx = 0; - while(buff.size() > MAX_SIZE) + while(buff.size() > MAX_SIZE && idx < buff.size()) { if(buff.get(idx).MotionButton) { diff --git a/Android/app/src/main/java/com/example/androidsteering/Motion.java b/Android/app/src/main/java/com/example/androidsteering/Motion.java index ad75453..a0f5ed6 100644 --- a/Android/app/src/main/java/com/example/androidsteering/Motion.java +++ b/Android/app/src/main/java/com/example/androidsteering/Motion.java @@ -116,6 +116,8 @@ private void update() updateRoll(roll); globalBuffer.addData(readPitch(), readRoll()); + + Log.d(mainActivity.getString(R.string.logTagMotion), "Sensor data update"); } // update pitch diff --git a/Windows/SteeringWheel/Connection.cs b/Windows/SteeringWheel/Connection.cs index ef8c2f7..914bf56 100644 --- a/Windows/SteeringWheel/Connection.cs +++ b/Windows/SteeringWheel/Connection.cs @@ -18,34 +18,57 @@ namespace SteeringWheel /// public class SharedBuffer { - private const int MAX_SIZE = 50; - private readonly Queue buffer = new Queue(); + private const int MAX_SIZE = 20; + private readonly List buffer = new List(); public void AddData(bool v1, int v2, float v3) { lock(buffer) { - buffer.Enqueue(new MotionData() + buffer.Add(new MotionData() { IsButton = v1, Status = v2, Value = v3 }); - while (buffer.Count > MAX_SIZE) buffer.Dequeue(); + int idx = 0; + while (buffer.Count > MAX_SIZE && idx < buffer.Count) + { + if (buffer[idx].IsButton) + { + idx++; + continue; + } + buffer.RemoveAt(idx); + } } } public void AddData(MotionData data) { lock(buffer) { - buffer.Enqueue(data); - while (buffer.Count > MAX_SIZE) buffer.Dequeue(); + buffer.Add(data); + int idx = 0; + while (buffer.Count > MAX_SIZE && idx < buffer.Count) + { + if (buffer[idx].IsButton) + { + idx++; + continue; + } + buffer.RemoveAt(idx); + } } } public MotionData GetData() { lock(buffer) { - if (buffer.Count > 0) return buffer.Dequeue(); + if (buffer.Count > 0) + { + MotionData data = buffer[0]; + buffer.RemoveAt(0); + return data; + } else return null; } } @@ -82,7 +105,7 @@ class Connection private readonly int MAX_WAIT_TIME = 1500; private readonly int DATA_SEPARATOR = 10086; - private readonly int BUFFER_SIZE = 13 * 10; // 10 data packs each time + private readonly int BUFFER_SIZE = 13 * 5; // 5 data packs each time private readonly int DEVICE_CHECK_EXPECTED = 123456; private readonly int DEVICE_CHECK_DATA = 654321; private bool isConnectionAllowed = false; diff --git a/Windows/SteeringWheel/Controller.cs b/Windows/SteeringWheel/Controller.cs index 278386e..27fcca0 100644 --- a/Windows/SteeringWheel/Controller.cs +++ b/Windows/SteeringWheel/Controller.cs @@ -78,6 +78,7 @@ class Controller private readonly MainWindow mainWindow; private readonly SharedBuffer sharedBuffer; private Thread processThread; + private Thread updateThread; private readonly int MAX_WAIT_TIME = 1500; private bool isProcessAllowed = false; private readonly float CAP_Steering = 60.0f; //treat max angle as 60 even though it can reach 90 @@ -86,10 +87,13 @@ class Controller // vjoy related private readonly vJoy joystick; + private vJoy.JoystickState joyReport; + private readonly object joyReportLock = new object(); private uint joystickID; private long axisMax = 0; public bool vJoyInitialized { get; private set; } - private const int triggerInterval = 50; + private const int triggerInterval = 100; + private const int updateInterval = 10; public Controller(MainWindow window, SharedBuffer buffer) { @@ -98,9 +102,13 @@ public Controller(MainWindow window, SharedBuffer buffer) vJoyInitialized = false; joystick = new vJoy(); - if(joystick.vJoyEnabled()) SetupVJoy(); - - SetupProcess(); + joyReport = new vJoy.JoystickState(); + if (joystick.vJoyEnabled()) + { + SetupVJoy(); + SetupProcess(); + SetupUpdate(); + } } /// @@ -113,11 +121,16 @@ public void Destroy() { if (!processThread.Join(MAX_WAIT_TIME)) processThread.Abort(); } + if (updateThread != null && updateThread.IsAlive) + { + if (!updateThread.Join(MAX_WAIT_TIME)) updateThread.Abort(); + } + joystick.ResetAll(); joystick.RelinquishVJD(joystickID); } /// - /// setup background process that keeps running + /// setup background thread that write joystate /// private void SetupProcess() { @@ -129,7 +142,7 @@ private void SetupProcess() var data = sharedBuffer.GetData(); if(data == null) { - Thread.Sleep(1); + Thread.Sleep(5); continue; } if(data.IsButton) @@ -194,6 +207,31 @@ private void SetupProcess() processThread.Start(); } + /// + /// set up background thread that updates vJoy state + /// + private void SetupUpdate() + { + isProcessAllowed = true; + updateThread = new Thread(() => + { + while(isProcessAllowed) + { + lock(joyReportLock) + { + if (!joystick.UpdateVJD(joystickID, ref joyReport)) + { + AddLog("Failed to update vJoy controller state"); + Debug.WriteLine("[Controller] updateThread failed to update VJD"); + } + } + Thread.Sleep(updateInterval); + } + }); + updateThread.Priority = ThreadPriority.AboveNormal; + updateThread.Start(); + } + /// /// process acceleration based on input value /// @@ -202,22 +240,33 @@ private void ProcessAcceleration(float val) { if(-40.0f <= val && val <= -30.0f) { - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_Z); - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_RZ); + lock(joyReportLock) + { + joyReport.AxisZ = 0; + joyReport.AxisZRot = 0; + } } else if(-90.0f <= val && val < -40.0f) { // forward - float step = SmoothStep(40.0f, CAP_AccForward, -val); + float step = FilterLinear(-val, 40.0f, CAP_AccForward); val = axisMax * step; - joystick.SetAxis((int)val, joystickID, HID_USAGES.HID_USAGE_RZ); + lock(joyReportLock) + { + joyReport.AxisZ = 0; + joyReport.AxisZRot = (int)val; + } } else if(-30.0f < val && val <= 90.0f) { // backward - float step = SmoothStep(-30.0f, CAP_AccBackward, val); + float step = FilterLinear(val, -30.0f, CAP_AccBackward); val = axisMax * step; - joystick.SetAxis((int)val, joystickID, HID_USAGES.HID_USAGE_Z); + lock (joyReportLock) + { + joyReport.AxisZRot = 0; + joyReport.AxisZ = (int)val; + } } } @@ -230,21 +279,30 @@ private void ProcessSteering(float val) if(-2.0f <= val && val <= 2.0f) { // set to rest mode - joystick.SetAxis((int)(axisMax / 2), joystickID, HID_USAGES.HID_USAGE_X); + lock (joyReportLock) + { + joyReport.AxisX = (int)(axisMax / 2); + } } else if(2.0f < val && val <= 90.0f) { // turning left - float step = SmoothStep(2.0f, CAP_Steering, val); + float step = FilterSmoothStep(val, 2.0f, CAP_Steering); float half = axisMax / 2.0f * step; - joystick.SetAxis((int)(axisMax / 2.0f - half), joystickID, HID_USAGES.HID_USAGE_X); + lock (joyReportLock) + { + joyReport.AxisX = (int)(axisMax / 2.0f - half); + } } else if(-90.0f <= val && val < -2.0f) { // turning right - float step = SmoothStep(2.0f, CAP_Steering, -val); + float step = FilterSmoothStep(-val, 2.0f, CAP_Steering); float half = axisMax / 2.0f * step; - joystick.SetAxis((int)(axisMax / 2.0f + half), joystickID, HID_USAGES.HID_USAGE_X); + lock (joyReportLock) + { + joyReport.AxisX = (int)(axisMax / 2.0f + half); + } } } @@ -267,6 +325,9 @@ private void SetupVJoy() { joystickID = i; joystick.GetVJDAxisMax(joystickID, HID_USAGES.HID_USAGE_X, ref axisMax); + joystick.ResetAll(); + joystick.SetAxis((int)(axisMax / 2.0f), joystickID, HID_USAGES.HID_USAGE_X); + joyReport.bDevice = (byte)joystickID; AddLog("vJoy valid device found\nID = " + joystickID); Debug.WriteLine("[Controller] SetupVJoy find valid device ID = " + joystickID); vJoyInitialized = true; @@ -309,11 +370,14 @@ public void TriggerControl(ControlButton button) { new Thread(() => { - lock (this) + lock (joyReportLock) { - joystick.SetBtn(true, joystickID, (uint)button); - Thread.Sleep(triggerInterval); - joystick.SetBtn(false, joystickID, (uint)button); + joyReport.Buttons |= (uint)(0x1 << ((int)button - 1)); + } + Thread.Sleep(triggerInterval); + lock (joyReportLock) + { + joyReport.Buttons &= ~(uint)(0x1 << ((int)button - 1)); } }).Start(); } @@ -326,66 +390,90 @@ public void TriggerControl(ControlAxis axis) { new Thread(() => { - lock (this) + lock (joyReportLock) { switch (axis) { case ControlAxis.POVUp: - joystick.SetDiscPov(0, joystickID, 1); - Thread.Sleep(triggerInterval); + joyReport.bHats = GetDiscPov(0); break; case ControlAxis.POVRight: - joystick.SetDiscPov(1, joystickID, 1); - Thread.Sleep(triggerInterval); + joyReport.bHats = GetDiscPov(1); break; case ControlAxis.POVDown: - joystick.SetDiscPov(2, joystickID, 1); - Thread.Sleep(triggerInterval); + joyReport.bHats = GetDiscPov(2); break; case ControlAxis.POVLeft: - joystick.SetDiscPov(3, joystickID, 1); - Thread.Sleep(triggerInterval); + joyReport.bHats = GetDiscPov(3); break; case ControlAxis.X: - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_X); - Thread.Sleep(triggerInterval); - joystick.SetAxis((int)(axisMax / 2), joystickID, HID_USAGES.HID_USAGE_X); + joyReport.AxisX = 0; break; case ControlAxis.XRot: - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_RX); - Thread.Sleep(triggerInterval); - joystick.SetAxis((int)(axisMax / 2), joystickID, HID_USAGES.HID_USAGE_RX); + joyReport.AxisXRot = (int)(axisMax / 2); break; case ControlAxis.Y: - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_Y); - Thread.Sleep(triggerInterval); - joystick.SetAxis((int)(axisMax / 2), joystickID, HID_USAGES.HID_USAGE_Y); + joyReport.AxisY = (int)(axisMax / 2); break; case ControlAxis.YRot: - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_RY); - Thread.Sleep(triggerInterval); - joystick.SetAxis((int)(axisMax / 2), joystickID, HID_USAGES.HID_USAGE_RY); + joyReport.AxisYRot = (int)(axisMax / 2); break; case ControlAxis.Z: - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_Z); - Thread.Sleep(triggerInterval); - joystick.SetAxis((int)(axisMax / 2), joystickID, HID_USAGES.HID_USAGE_Z); // Need to be half here - Thread.Sleep(triggerInterval); - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_Z); + joyReport.AxisZ = (int)(axisMax / 2); break; case ControlAxis.ZRot: - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_RZ); - Thread.Sleep(triggerInterval); - joystick.SetAxis((int)(axisMax / 2), joystickID, HID_USAGES.HID_USAGE_RZ); // Need to be half here - Thread.Sleep(triggerInterval); - joystick.SetAxis(0, joystickID, HID_USAGES.HID_USAGE_RZ); + joyReport.AxisZRot = (int)(axisMax / 2); + break; + } + } + Thread.Sleep(triggerInterval); + lock (joyReportLock) + { + switch (axis) + { + case ControlAxis.POVUp: + case ControlAxis.POVRight: + case ControlAxis.POVDown: + case ControlAxis.POVLeft: + joyReport.bHats = 0xFFFFFFFF; + break; + case ControlAxis.X: + joyReport.AxisX = (int)(axisMax / 2); + break; + case ControlAxis.XRot: + joyReport.AxisXRot = 0; + break; + case ControlAxis.Y: + joyReport.AxisY = 0; + break; + case ControlAxis.YRot: + joyReport.AxisYRot = 0; + break; + case ControlAxis.Z: + joyReport.AxisZ = 0; + break; + case ControlAxis.ZRot: + joyReport.AxisZRot = 0; break; } - joystick.ResetPovs(joystickID); } }).Start(); } + /// + /// get discontinuous POV data based on index + /// + /// + /// + private uint GetDiscPov(int idx) + { + byte b1 = (byte)((idx + 0) % 4); + byte b2 = (byte)((idx + 1) % 4); + byte b3 = (byte)((idx + 2) % 4); + byte b4 = (byte)((idx + 3) % 4); + return (uint)(b4 << 12) | (uint)(b3 << 8) | (uint)(b2 << 4) | (uint)b1; + } + /// /// add log message to main window /// @@ -398,14 +486,18 @@ private void AddLog(string message) })); } - /// - /// compute smoothstep + // linear filter + public static float FilterLinear(float val, float edge0, float edge1) + { + float x = (val - edge0) / (edge1 - edge0); + x = x < 0.0f ? 0.0f : x; + x = x > 1.0f ? 1.0f : x; + return x; + } + + /// smoothstep filter /// Reference: https://en.wikipedia.org/wiki/Smoothstep - /// - /// - /// - /// - public static float SmoothStep(float edge0, float edge1, float val) + public static float FilterSmoothStep(float val, float edge0, float edge1) { float x = (val - edge0) / (edge1 - edge0); x = x < 0.0f ? 0.0f : x; diff --git a/Windows/SteeringWheel/MainWindow.xaml.cs b/Windows/SteeringWheel/MainWindow.xaml.cs index 006b05d..875112b 100644 --- a/Windows/SteeringWheel/MainWindow.xaml.cs +++ b/Windows/SteeringWheel/MainWindow.xaml.cs @@ -68,8 +68,7 @@ private void SetupNotifyIcon() { Show(); WindowState = WindowState.Normal; - ConfigureWindow configureWindow = new ConfigureWindow(this); - configureWindow.ShowDialog(); + LaunchConfigureWindow(); } ); notifyIcon.ContextMenu.MenuItems.Add( @@ -220,8 +219,18 @@ public void UpdateConnectButton() /// /// private void ConfigureButton_Click(object sender, RoutedEventArgs e) + { + LaunchConfigureWindow(); + } + + /// + /// launch configure window + /// + private void LaunchConfigureWindow() { ConfigureWindow configureWindow = new ConfigureWindow(this); + configureWindow.ShowInTaskbar = false; + configureWindow.Owner = this; configureWindow.ShowDialog(); }