public int StartAndRegisterFFB(AFFBManager ffb)
        {
            // Start FFB
#if FFB
            if (Joystick.IsDeviceFfb(joyID))
            {
#if false // obsolete
                bool Ffbstarted = Joystick.FfbStart(joyID);
                if (!Ffbstarted)
                {
                    Console.WriteLine("Failed to start FFB on vJoy device number {0}.", joyID);
                    Console.ReadKey();
                    return(-3);
                }
                else
                {
                    Console.WriteLine("Started FFB on vJoy device number {0} - OK", joyID);
                }
#endif

                // Register Generic callback function
                // At this point you instruct the Receptor which callback function to call with every FFB packet it receives
                // It is the role of the designer to register the right FFB callback function

                // Note from me:
                // Warning: the callback is called in the context of a thread started by vJoyInterface.dll
                // when opening the joystick. This thread blocks upon a new system event from the driver.
                // It is perfectly ok to do some work in it, but do not overload it to avoid
                // loosing/desynchronizing FFB packets from the third party application.
                FFBReceiver.RegisterBaseCallback(Joystick, joyID, ffb);
            }
#endif // FFB
            return(0);
        }
Esempio n. 2
0
 /// <summary>
 /// Registers the base callback if not yet registered.
 /// </summary>
 public void RegisterBaseCallback(vJoy joystick, AFFBManager ffb)
 {
     FFBManager = ffb;
     Joystick   = joystick;
     if (!isRegistered)
     {
         wrapper = FfbFunction1; //needed to keep a reference!
         Joystick.FfbRegisterGenCB(wrapper, IntPtr.Zero);
         isRegistered = true;
     }
 }
Esempio n. 3
0
        /// <summary>
        /// Registers the base callback if not yet registered.
        /// </summary>
        public void RegisterBaseCallback(vJoy joystick, uint id, AFFBManager ffb)
        {
            this.FFBManager = ffb;
            this.Joystick   = joystick;
            this.Id         = id;
            // Read PID block
            this.Joystick.FfbReadPID(this.Id, ref this.PIDBlock);

            if (!isRegistered)
            {
                this.wrapper = this.FfbFunction1; //needed to keep a reference!
                Joystick.FfbRegisterGenCB(this.wrapper, IntPtr.Zero);
                this.isRegistered = true;
            }
        }
Esempio n. 4
0
        protected void ManagerThreadMethod()
        {
__restart:

            Log("Program configured for " + Config.TranslatingModes, LogLevels.IMPORTANT);

            var boards = USBSerialIO.ScanAllCOMPortsForIOBoards();

            if (boards.Length > 0)
            {
                IOboard = boards[0];
                Log("Found io board on " + IOboard.COMPortName + " version=" + IOboard.BoardVersion + " type=" + IOboard.BoardDescription, LogLevels.IMPORTANT);
            }
            else
            {
                IOboard = null;
                if (Config.RunWithoutIOBoard)
                {
                    Log("No boards found! Continue without real hardware", LogLevels.ERROR);
                }
                else
                {
                    Log("No boards found! Thread will terminate", LogLevels.ERROR);
                    Running = false;
                    //Console.ReadKey(true);
                    return;
                }
            }

            // Output system : lamps
            Outputs = new MAMEOutputWinAgent();
            Outputs.Start();

            switch (Config.TranslatingModes)
            {
            case FFBTranslatingModes.PWM_CENTERED:
            case FFBTranslatingModes.PWM_DIR: {
                FFB = new FFBManagerTorque(GlobalRefreshPeriod_ms);
            }
            break;

            case FFBTranslatingModes.MODEL3_UNKNOWN_DRVBD: {
                // Default to Scud/Daytona2
                FFB = new FFBManagerModel3Scud(GlobalRefreshPeriod_ms);
            }
            break;

            case FFBTranslatingModes.MODEL3_LEMANS_DRVBD: {
                FFB = new FFBManagerModel3Lemans(GlobalRefreshPeriod_ms);
            }
            break;

            case FFBTranslatingModes.MODEL3_SCUD_DRVBD: {
                FFB = new FFBManagerModel3Scud(GlobalRefreshPeriod_ms);
            }
            break;

            default:
                throw new NotImplementedException("Unsupported FFB mode " + Config.TranslatingModes.ToString());
            }


            // Use this to allow 1ms sleep granularity (else default is 16ms!!!)
            // This consumes more CPU cycles in the OS, but does improve
            // a lot reactivity when soft real-time work needs to be done.
            MultimediaTimer.SetTickGranularityOnWindows();

            vJoy.EnablevJoy();             // Create joystick interface
            vJoy.Acquire(1);               // Use first enumerated vJoy device
            vJoy.StartAndRegisterFFB(FFB); // Start FFB callback mechanism in vJoy

            // In case we want to use XInput/DInput devices to gather multiple inputs?
            //XInput();
            //DirectInput();

            if (IOboard != null)
            {
                Log("Initializing IO board", LogLevels.IMPORTANT);
                // Initialize board
                IOboard.PerformInit();
                // Enable safety watchdog
                IOboard.EnableWD();
                // Enable auto-streaming
                IOboard.StartStreaming();
            }

            if (Config.VerbosevJoyManager)
            {
                Log("Start feeding...");
            }

            // Start FFB manager
            FFB.Start();

            // Internal values for special operation
            double prev_angle = 0.0;

            UInt32 autofire_mode_on = 0;

            uint   error_counter = 0;
            UInt64 nextRun_ms    = (UInt64)(MultimediaTimer.RefTimer.ElapsedMilliseconds);

            while (Running)
            {
                TickCount++;

                nextRun_ms += GlobalRefreshPeriod_ms;
                UInt64 now      = (UInt64)(MultimediaTimer.RefTimer.ElapsedMilliseconds);
                int    delay_ms = (int)(nextRun_ms - now);
                if (delay_ms >= 0)
                {
                    // Sleep until next tick
                    Thread.Sleep(delay_ms);
                }
                else
                {
                    if (Config.VerbosevJoyManager)
                    {
                        Log("One period missed by " + (-delay_ms) + "ms", LogLevels.DEBUG);
                    }
                }
                if (IOboard != null)
                {
                    try {
                        if (IOboard.IsOpen)
                        {
                            // Empty serial buffer
                            if (delay_ms < 0)
                            {
                                IOboard.UpdateOnStreaming((-delay_ms) / GlobalRefreshPeriod_ms);
                            }
                            // Shift tick to synch with IOboard
                            var before = MultimediaTimer.RefTimer.ElapsedMilliseconds;
                            // Update status on received packets
                            var nbproc = IOboard.UpdateOnStreaming();
                            var after  = MultimediaTimer.RefTimer.ElapsedMilliseconds;
                            // Delay is expected to be 1-2ms for processing in stream
                            delay_ms = (int)(after - before);
                            // Accept up to 2ms of delay (jitter), else consider we have
                            if (delay_ms > 2 && nbproc == 1)
                            {
                                var add_delay = Math.Min(GlobalRefreshPeriod_ms - 1, delay_ms - 1);
                                add_delay   = 1;
                                nextRun_ms += (ulong)add_delay;
                                if (Config.VerbosevJoyManager)
                                {
                                    Log("Read took " + delay_ms + "ms delay, adding " + add_delay + "ms to sync with IO board serial port", LogLevels.DEBUG);
                                }
                            }

                            if (Config.VerbosevJoyManager)
                            {
                                if (nbproc > 1)
                                {
                                    Log("Processed " + nbproc + " msg instead of 1", LogLevels.DEBUG);
                                }
                            }
                            // Refresh wheel angle (between -1...1)
                            if (IOboard.AnalogInputs.Length > 0)
                            {
                                // Scale analog input between 0..0xFFF, then map it to -1/+1, 0 being center
                                var angle_u = ((double)IOboard.AnalogInputs[0]) * (2.0 / (double)0xFFF) - 1.0;
                                // Refresh values in FFB manager
                                if (IOboard.WheelStates.Length > 0)
                                {
                                    // If full state given by IO board (should be in unit_per_s!)
                                    FFB.RefreshCurrentState(angle_u, IOboard.WheelStates[0], IOboard.WheelStates[1]);
                                }
                                else
                                {
                                    // If only periodic position
                                    FFB.RefreshCurrentPosition(angle_u);
                                }
                                prev_angle = angle_u;
                            }

                            // For debugging purpose, add a 4th axis to display torque output
                            uint[] axesXYRZplusSL0ForTrq = new uint[5];
                            IOboard.AnalogInputs.CopyTo(axesXYRZplusSL0ForTrq, 0);
                            axesXYRZplusSL0ForTrq[4] = (uint)(FFB.OutputTorqueLevel * 0x7FF + 0x800);

                            // Set values into vJoy report:
                            // - axes
                            vJoy.UpdateAxes12(axesXYRZplusSL0ForTrq);

                            // - buttons (only32 supported for now)
                            if (IOboard.DigitalInputs8.Length > 0)
                            {
                                UInt32 rawinput_states = 0;
                                int    rawidx          = 0;
                                // For each single input, process mapping, autofire and toggle
                                for (int i = 0; i < Math.Min(4, IOboard.DigitalInputs8.Length); i++)
                                {
                                    // Scan 8bit input block
                                    for (int j = 0; j < 8; j++)
                                    {
                                        // Default input value is current logic (false if not inverted)
                                        bool newrawval = Config.RawInputTovJoyMap[rawidx].IsInvertedLogic;

                                        // Check if input is "on" and invert default value
                                        if ((IOboard.DigitalInputs8[i] & (1 << j)) != 0)
                                        {
                                            // If was false, then set true
                                            newrawval = !newrawval;
                                        }
                                        // Now newrawval is the raw state of the input taking into account inv.logic

                                        // Bit corresponding to this input
                                        var rawbit = (UInt32)(1 << rawidx);
                                        // Store new state of raw input
                                        if (newrawval)
                                        {
                                            rawinput_states |= rawbit;
                                        }

                                        // Previous state of this input (for transition detection)
                                        var prev_state = (RawInputsStates & rawbit) != 0;

                                        // Check if we toggle the bit (or autofire mode)
                                        if (Config.RawInputTovJoyMap[rawidx].IsToggle)
                                        {
                                            // Toggle only if we detect a false->true transition in raw value
                                            if (newrawval && (!prev_state))
                                            {
                                                // Toggle = xor on every vJoy buttons
                                                vJoy.ToggleButtons(Config.RawInputTovJoyMap[rawidx].vJoyBtns);
                                            }
                                        }
                                        else if (Config.RawInputTovJoyMap[rawidx].IsAutoFire)
                                        {
                                            // Autofire set, if false->true transition, then toggle autofire state
                                            if (newrawval && (!prev_state))
                                            {
                                                // Enable/disable autofire
                                                autofire_mode_on ^= rawbit;
                                            }
                                            // No perform autofire toggle if autofire enabled
                                            if ((autofire_mode_on & rawbit) != 0)
                                            {
                                                // Toggle = xor every 20 periods
                                                if ((TickCount % 20) == 0)
                                                {
                                                    vJoy.ToggleButtons(Config.RawInputTovJoyMap[rawidx].vJoyBtns);
                                                }
                                            }
                                        }
                                        else
                                        {
                                            // No toggle, no autofire : perform simple mask
                                            if (newrawval)
                                            {
                                                vJoy.SetButtons(Config.RawInputTovJoyMap[rawidx].vJoyBtns);
                                            }
                                            else
                                            {
                                                vJoy.ClearButtons(Config.RawInputTovJoyMap[rawidx].vJoyBtns);
                                            }
                                        }

                                        // Next input
                                        rawidx++;
                                    }
                                }

                                // Save raw input state for next run
                                RawInputsStates = rawinput_states;
                            }

                            // - 360deg POV to view for wheel angle
                            //vJoy.UpodateContinuousPOV((uint)((IOboard.AnalogInputs[0] / (double)0xFFF) * 35900.0) + 18000);

                            // Update vJoy and send to driver every n ticks to limit workload on driver
                            if ((TickCount % vJoyUpdate) == 0)
                            {
                                vJoy.PublishiReport();
                            }

                            // Outputs (Lamps)
                            if (Outputs != null)
                            {
                                // First 2 bits are unused for lamps (used by PWM Fwd/Rev)
                                IOboard.DigitalOutputs8[0] = (byte)(Outputs.LampsValue);
                            }

                            // Now output torque to Pwm+Dir or drive board command
                            switch (Config.TranslatingModes)
                            {
                            // PWM centered mode (50% = 0 torque)
                            case FFBTranslatingModes.PWM_CENTERED: {
                                // Latch a copy
                                var outlevel = FFB.OutputTorqueLevel;
                                // Enforce range again to be [-1; 1]
                                outlevel = Math.Min(1.0, Math.Max(outlevel, -1.0));
                                UInt16 analogOut = (UInt16)(outlevel * 0x7FF + 0x800);
                                IOboard.AnalogOutputs[0] = analogOut;
                            }
                            break;

                            // PWM+dir mode (0% = 0 torque, direction given by first output)
                            case FFBTranslatingModes.PWM_DIR: {
                                // Latch a copy
                                var outlevel = FFB.OutputTorqueLevel;
                                if (outlevel >= 0.0)
                                {
                                    UInt16 analogOut = (UInt16)(outlevel * 0xFFF);
                                    // Save into IOboard
                                    IOboard.AnalogOutputs[0]    = analogOut;
                                    IOboard.DigitalOutputs8[0] |= 1 << 0;       // set FwdCmd bit 0
                                    IOboard.DigitalOutputs8[0] &= 0xFD;         // clear RevCmd bit 1
                                }
                                else
                                {
                                    UInt16 analogOut = (UInt16)(-outlevel * 0xFFF);
                                    // Save into IOboard
                                    IOboard.AnalogOutputs[0]    = analogOut;
                                    IOboard.DigitalOutputs8[0] |= 1 << 1;       // set RevCmd bit 1
                                    IOboard.DigitalOutputs8[0] &= 0xFE;         // clear FwdCmd bit 0
                                }
                            }
                            break;

                            // Driveboard translation mode
                            case FFBTranslatingModes.MODEL3_UNKNOWN_DRVBD:
                            case FFBTranslatingModes.MODEL3_LEMANS_DRVBD:
                            case FFBTranslatingModes.MODEL3_SCUD_DRVBD: {
                                // Latch a copy
                                var outlevel = FFB.OutputEffectCommand;
                                if (IOboard.DigitalOutputs8.Length > 1)
                                {
                                    IOboard.DigitalOutputs8[1] = (byte)(outlevel & 0xFF);
                                }
                            }
                            break;
                            }

                            // Save output state
                            RawOutputsStates = 0;
                            for (int i = 0; i < IOboard.DigitalOutputs8.Length; i++)
                            {
                                var shift = (i << 3);
                                RawOutputsStates = (UInt32)(IOboard.DigitalOutputs8[i] << shift);
                            }

                            // Send all outputs - this will revive the watchdog!
                            IOboard.SendOutputs();
                        }
                        else
                        {
                            Log("Re-connecting to same IO board on port " + IOboard.COMPortName, LogLevels.IMPORTANT);
                            IOboard.OpenComm();
                            // Enable safety watchdog
                            IOboard.EnableWD();
                            // Enable auto-streaming
                            IOboard.StartStreaming();
                            error_counter = 0;
                        }
                    } catch (Exception ex) {
                        Log("IO board Failing with " + ex.Message, LogLevels.ERROR);
                        try {
                            if (IOboard.IsOpen)
                            {
                                IOboard.CloseComm();
                            }
                        } catch (Exception ex2) {
                            Log("Unable to close communication " + ex2.Message, LogLevels.ERROR);
                        }
                        error_counter++;
                        if (error_counter > 10)
                        {
                            // Serious problem here, try complete restart with scanning
                            FFB.Stop();
                            goto __restart;
                        }
                        System.Threading.Thread.Sleep(500);
                    }
                }
            }
            ;

            MultimediaTimer.RestoreTickGranularityOnWindows();

            if (Outputs != null)
            {
                Outputs.Stop();
            }
            FFB.Stop();
            if (IOboard != null)
            {
                IOboard.CloseComm();
            }
            vJoy.Release();
        }