// vJoyClient.cpp : Simple feeder application with a FFB demo // // Monitor Force Feedback (FFB) vJoy device #include "stdafx.h" //#include "Devioctl.h" #include "public.h" #include #include #include #include "vjoyinterface.h" #include "Math.h" // Default device ID (Used when ID not specified) #define DEV_ID 1 // Prototypes void CALLBACK FfbFunction(PVOID data); void CALLBACK FfbFunction1(PVOID cb, PVOID data); BOOL PacketType2Str(FFBPType Type, LPTSTR Str); BOOL EffectType2Str(FFBEType Ctrl, LPTSTR Str); BOOL DevCtrl2Str(FFB_CTRL Type, LPTSTR Str); BOOL EffectOpStr(FFBOP Op, LPTSTR Str); int Polar2Deg(BYTE Polar); int Byte2Percent(BYTE InByte); int TwosCompByte2Int(BYTE in); int ffb_direction = 0; int ffb_strenght = 0; int serial_result = 0; JOYSTICK_POSITION_V2 iReport; // The structure that holds the full position data int __cdecl _tmain(int argc, _TCHAR* argv[]) { int stat = 0; UINT DevID = DEV_ID; USHORT X = 0; USHORT Y = 0; USHORT Z = 0; LONG Btns = 0; PVOID pPositionMessage; UINT IoCode = LOAD_POSITIONS; UINT IoSize = sizeof(JOYSTICK_POSITION); // HID_DEVICE_ATTRIBUTES attrib; BYTE id = 1; UINT iInterface = 1; // Define the effect names static FFBEType FfbEffect= (FFBEType)-1; LPCTSTR FfbEffectName[] = {"NONE", "Constant Force", "Ramp", "Square", "Sine", "Triangle", "Sawtooth Up",\ "Sawtooth Down", "Spring", "Damper", "Inertia", "Friction", "Custom Force"}; // Set the target Joystick - get it from the command-line if (argc>1) DevID = _tstoi(argv[1]); // Get the driver attributes (Vendor ID, Product ID, Version Number) if (!vJoyEnabled()) { _tprintf("Function vJoyEnabled Failed - make sure that vJoy is installed and enabled\n"); int dummy = getchar(); stat = - 2; goto Exit; } else { wprintf(L"Vendor: %s\nProduct :%s\nVersion Number:%s\n", static_cast (GetvJoyManufacturerString()), static_cast(GetvJoyProductString()), static_cast(GetvJoySerialNumberString())); }; // Get the status of the vJoy device before trying to acquire it VjdStat status = GetVJDStatus(DevID); switch (status) { case VJD_STAT_OWN: _tprintf("vJoy device %d is already owned by this feeder\n", DevID); break; case VJD_STAT_FREE: _tprintf("vJoy device %d is free\n", DevID); break; case VJD_STAT_BUSY: _tprintf("vJoy device %d is already owned by another feeder\nCannot continue\n", DevID); return -3; case VJD_STAT_MISS: _tprintf("vJoy device %d is not installed or disabled\nCannot continue\n", DevID); return -4; default: _tprintf("vJoy device %d general error\nCannot continue\n", DevID); return -1; }; // Acquire the vJoy device if (!AcquireVJD(DevID)) { _tprintf("Failed to acquire vJoy device number %d.\n", DevID); int dummy = getchar(); stat = -1; goto Exit; } else _tprintf("Acquired device number %d - OK\n", DevID); // Start FFB #if 1 BOOL Ffbstarted = FfbStart(DevID); if (!Ffbstarted) { _tprintf("Failed to start FFB on vJoy device number %d.\n", DevID); int dummy = getchar(); stat = -3; goto Exit; } else _tprintf("Started FFB on vJoy device number %d - OK\n", DevID); #endif // 1 // 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 FfbRegisterGenCB(FfbFunction1, NULL); // Start endless loop // The loop injects position data to the vJoy device // If it fails it let's the user try again // // FFB Note: // All FFB activity is performed in a separate thread created when registered the callback function while (1) { // Set destenition vJoy device id = (BYTE)DevID; iReport.bDevice = id; // Set position data of 3 first axes if (Z>35000) Z=0; Z += 200; iReport.wAxisZ = Z; iReport.wAxisX = 32000-Z; iReport.wAxisY = Z/2+7000; // Set position data of first 8 buttons Btns = 1<<(Z/4000); iReport.lButtons = Btns; // Send position data to vJoy device pPositionMessage = (PVOID)(&iReport); if (!UpdateVJD(DevID, pPositionMessage)) { printf("Feeding vJoy device number %d failed - try to enable device then press enter\n", DevID); getchar(); AcquireVJD(DevID); } Sleep(2); } Exit: RelinquishVJD(DevID); return 0; } // Generic callback function void CALLBACK FfbFunction(PVOID data) { FFB_DATA * FfbData = (FFB_DATA *)data; int size = FfbData->size; _tprintf("\nFFB Size %d\n", size); _tprintf("Cmd:%08.8X ", FfbData->cmd); _tprintf("ID:%02.2X ", FfbData->data[0]); _tprintf("Size:%02.2d ", static_cast(FfbData->size - 8)); _tprintf(" - "); for (UINT i = 0; i < FfbData->size - 8; i++) _tprintf(" %02.2X", (UINT)FfbData->data); _tprintf("\n"); } void CALLBACK FfbFunction1(PVOID data, PVOID userdata) { // Packet Header _tprintf("\n ============= FFB Packet size Size %d =============\n", static_cast(((FFB_DATA *)data)->size)); /////// Packet Device ID, and Type Block Index (if exists) #pragma region Packet Device ID, and Type Block Index int DeviceID, BlockIndex; FFBPType Type; TCHAR TypeStr[100]; if (ERROR_SUCCESS == Ffb_h_DeviceID((FFB_DATA *)data, &DeviceID)) _tprintf("\n > Device ID: %d", DeviceID); if (ERROR_SUCCESS == Ffb_h_Type((FFB_DATA *)data, &Type)) { if (!PacketType2Str(Type, TypeStr)) _tprintf("\n > Packet Type: %d", Type); else _tprintf("\n > Packet Type: %s", TypeStr); } if (ERROR_SUCCESS == Ffb_h_EBI((FFB_DATA *)data, &BlockIndex)) _tprintf("\n > Effect Block Index: %d", BlockIndex); #pragma endregion /////// Effect Report #pragma region Effect Report FFB_EFF_CONST Effect; if (ERROR_SUCCESS == Ffb_h_Eff_Report((FFB_DATA *)data, &Effect)) { if (!EffectType2Str(Effect.EffectType, TypeStr)) _tprintf("\n >> Effect Report: %02x", Effect.EffectType); else _tprintf("\n >> Effect Report: %s", TypeStr); if (Effect.Polar) { _tprintf("\n >> Direction: %d deg (%02x)", Polar2Deg(Effect.Direction), Effect.Direction); } else { _tprintf("\n >> X Direction: %02x", Effect.DirX); _tprintf("\n >> Y Direction: %02x", Effect.DirY); }; if (Effect.Duration == 0xFFFF) _tprintf("\n >> Duration: Infinit"); else _tprintf("\n >> Duration: %d MilliSec", static_cast(Effect.Duration)); if (Effect.TrigerRpt == 0xFFFF) _tprintf("\n >> Trigger Repeat: Infinit"); else _tprintf("\n >> Trigger Repeat: %d", static_cast(Effect.TrigerRpt)); if (Effect.SamplePrd == 0xFFFF) _tprintf("\n >> Sample Period: Infinit"); else _tprintf("\n >> Sample Period: %d", static_cast(Effect.SamplePrd)); _tprintf("\n >> Gain: %d%%", Byte2Percent(Effect.Gain)); }; #pragma endregion #pragma region PID Device Control FFB_CTRL Control; TCHAR CtrlStr[100]; if (ERROR_SUCCESS == Ffb_h_DevCtrl((FFB_DATA *)data, &Control) && DevCtrl2Str(Control, CtrlStr)) _tprintf("\n >> PID Device Control: %s", CtrlStr); #pragma endregion #pragma region Effect Operation FFB_EFF_OP Operation; TCHAR EffOpStr[100]; if (ERROR_SUCCESS == Ffb_h_EffOp((FFB_DATA *)data, &Operation) && EffectOpStr(Operation.EffectOp, EffOpStr)) { _tprintf("\n >> Effect Operation: %s", EffOpStr); if (Operation.LoopCount == 0xFF) _tprintf("\n >> Loop until stopped"); else _tprintf("\n >> Loop %d times", static_cast(Operation.LoopCount)); }; #pragma endregion #pragma region Global Device Gain BYTE Gain; if (ERROR_SUCCESS == Ffb_h_DevGain((FFB_DATA *)data, &Gain)) _tprintf("\n >> Global Device Gain: %d", Byte2Percent(Gain)); #pragma endregion #pragma region Condition FFB_EFF_COND Condition; if (ERROR_SUCCESS == Ffb_h_Eff_Cond((FFB_DATA *)data, &Condition)) { if (Condition.isY) _tprintf("\n >> Y Axis"); else _tprintf("\n >> X Axis"); _tprintf("\n >> Center Point Offset: %d", TwosCompByte2Int(Condition.CenterPointOffset)*10000/127); _tprintf("\n >> Positive Coefficient: %d", TwosCompByte2Int(Condition.PosCoeff)*10000/127); _tprintf("\n >> Negative Coefficient: %d", TwosCompByte2Int(Condition.NegCoeff)*10000/127); _tprintf("\n >> Positive Saturation: %d", Condition.PosSatur*10000/255); _tprintf("\n >> Negative Saturation: %d", Condition.NegSatur*10000/255); _tprintf("\n >> Dead Band: %d", Condition.DeadBand*10000/255); } #pragma endregion #pragma region Envelope FFB_EFF_ENVLP Envelope; if (ERROR_SUCCESS == Ffb_h_Eff_Envlp((FFB_DATA *)data, &Envelope)) { _tprintf("\n >> Attack Level: %d", Envelope.AttackLevel*10000/255); _tprintf("\n >> Fade Level: %d", Envelope.FadeLevel*10000/255); _tprintf("\n >> Attack Time: %d", static_cast(Envelope.AttackTime)); _tprintf("\n >> Fade Time: %d", static_cast(Envelope.FadeTime)); }; #pragma endregion #pragma region Periodic FFB_EFF_PERIOD EffPrd; if (ERROR_SUCCESS == Ffb_h_Eff_Period((FFB_DATA *)data, &EffPrd)) { _tprintf("\n >> Magnitude: %d", EffPrd.Magnitude * 10000 / 255); _tprintf("\n >> Offset: %d", TwosCompByte2Int(EffPrd.Offset) * 10000 / 127); _tprintf("\n >> Phase: %d", EffPrd.Phase * 3600 / 255); _tprintf("\n >> Period: %d", static_cast(EffPrd.Period)); }; #pragma endregion #pragma region Effect Type FFBEType EffectType; if (ERROR_SUCCESS == Ffb_h_EffNew((FFB_DATA *)data, &EffectType)) { if (EffectType2Str(EffectType, TypeStr)) _tprintf("\n >> Effect Type: %s", TypeStr); else _tprintf("\n >> Effect Type: Unknown"); } #pragma endregion #pragma region Ramp Effect FFB_EFF_RAMP RampEffect; if (ERROR_SUCCESS == Ffb_h_Eff_Ramp((FFB_DATA *)data, &RampEffect)) { _tprintf("\n >> Ramp Start: %d", TwosCompByte2Int(RampEffect.Start) * 10000 / 127); _tprintf("\n >> Ramp End: %d", TwosCompByte2Int(RampEffect.End) * 10000 / 127); }; #pragma endregion _tprintf("\n"); FfbFunction(data); _tprintf("\n ====================================================\n"); } // Convert Packet type to String BOOL PacketType2Str(FFBPType Type, LPTSTR OutStr) { BOOL stat = TRUE; LPTSTR Str=""; switch (Type) { case PT_EFFREP: Str = "Effect Report"; break; case PT_ENVREP: Str = "Envelope Report"; break; case PT_CONDREP: Str = "Condition Report"; break; case PT_PRIDREP: Str = "Periodic Report"; break; case PT_CONSTREP: Str = "Constant Force Report"; break; case PT_RAMPREP: Str = "Ramp Force Report"; break; case PT_CSTMREP: Str = "Custom Force Data Report"; break; case PT_SMPLREP: Str = "Download Force Sample"; break; case PT_EFOPREP: Str = "Effect Operation Report"; break; case PT_BLKFRREP: Str = "PID Block Free Report"; break; case PT_CTRLREP: Str = "PID Device Contro"; break; case PT_GAINREP: Str = "Device Gain Report"; break; case PT_SETCREP: Str = "Set Custom Force Report"; break; case PT_NEWEFREP: Str = "Create New Effect Report"; break; case PT_BLKLDREP: Str = "Block Load Report"; break; case PT_POOLREP: Str = "PID Pool Report"; break; default: stat = FALSE; break; } if (stat) _tcscpy_s(OutStr, 100, Str); return stat; } // Convert Effect type to String BOOL EffectType2Str(FFBEType Type, LPTSTR OutStr) { BOOL stat = TRUE; LPTSTR Str=""; switch (Type) { case ET_NONE: stat = FALSE; break; case ET_CONST: Str="Constant Force"; break; case ET_RAMP: Str="Ramp"; break; case ET_SQR: Str="Square"; break; case ET_SINE: Str="Sine"; break; case ET_TRNGL: Str="Triangle"; break; case ET_STUP: Str="Sawtooth Up"; break; case ET_STDN: Str="Sawtooth Down"; break; case ET_SPRNG: Str="Spring"; break; case ET_DMPR: Str="Damper"; break; case ET_INRT: Str="Inertia"; break; case ET_FRCTN: Str="Friction"; break; case ET_CSTM: Str="Custom Force"; break; default: stat = FALSE; break; }; if (stat) _tcscpy_s(OutStr, 100, Str); return stat; } // Convert PID Device Control to String BOOL DevCtrl2Str(FFB_CTRL Ctrl, LPTSTR OutStr) { BOOL stat = TRUE; LPTSTR Str=""; switch (Ctrl) { case CTRL_ENACT: Str="Enable Actuators"; break; case CTRL_DISACT: Str="Disable Actuators"; break; case CTRL_STOPALL: Str="Stop All Effects"; break; case CTRL_DEVRST: Str="Device Reset"; break; case CTRL_DEVPAUSE: Str="Device Pause"; break; case CTRL_DEVCONT: Str="Device Continue"; break; default: stat = FALSE; break; } if (stat) _tcscpy_s(OutStr, 100, Str); return stat; } // Convert Effect operation to string BOOL EffectOpStr(FFBOP Op, LPTSTR OutStr) { BOOL stat = TRUE; LPTSTR Str=""; switch (Op) { case EFF_START: Str="Effect Start"; break; case EFF_SOLO: Str="Effect Solo Start"; break; case EFF_STOP: Str="Effect Stop"; break; default: stat = FALSE; break; } if (stat) _tcscpy_s(OutStr, 100, Str); return stat; } // Polar values (0x00-0xFF) to Degrees (0-360) int Polar2Deg(BYTE Polar) { return ((UINT)Polar*360)/255; } // Convert range 0x00-0xFF to 0%-100% int Byte2Percent(BYTE InByte) { return ((UINT)InByte*100)/255; } // Convert One-Byte 2's complement input to integer int TwosCompByte2Int(BYTE in) { int tmp; BYTE inv = ~in; BOOL isNeg = in>>7; if (isNeg) { tmp = (int)(inv); tmp = -1*tmp; return tmp; } else return (int)in; }