/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "PoKeys55.h" #include #include "mczapkie/mctools.h" //--------------------------------------------------------------------------- // HIDscaner: http://forum.simflight.com/topic/68257-latest-lua-package-for-fsuipc-and-wideclient/ //#define MY_DEVICE_ID "Vid_04d8&Pid_003F" //#define MY_DEVICE_ID "Vid_1dc3&Pid_1001&Rev_1000&MI_01" // HID\Vid_1dc3&Pid_1001&Rev_1000&MI_01 - MI_01 to jest interfejs komunikacyjny (00-joystick, // 02-klawiatura) HANDLE WriteHandle = INVALID_HANDLE_VALUE; HANDLE ReadHandle = INVALID_HANDLE_VALUE; // GUID InterfaceClassGuid={0x4d1e55b2,0xf16f,0x11cf,0x88,0xcb,0x00,0x11,0x11,0x00,0x00,0x30}; //{4d1e55b2-f16f-11cf-88cb-001111000030} TPoKeys55::TPoKeys55() { cRequest = 0; iPWMbits = 1; iFaza = 0; iLastCommand = 0; fAnalog[0] = fAnalog[1] = fAnalog[2] = fAnalog[3] = fAnalog[4] = fAnalog[5] = fAnalog[6] = -1.0; iPWM[0] = iPWM[1] = iPWM[2] = iPWM[3] = iPWM[4] = iPWM[5] = iPWM[6] = 0; iPWM[7] = 4096; iInputs[0] = 0; // czy normalnie są w stanie wysokim? iRepeats = 0; bNoError = true; }; //--------------------------------------------------------------------------- TPoKeys55::~TPoKeys55() { Close(); }; //--------------------------------------------------------------------------- void TPoKeys55::Close() { // rozłączenie komunikacji if (WriteHandle != INVALID_HANDLE_VALUE) CloseHandle(WriteHandle); WriteHandle = INVALID_HANDLE_VALUE; if (ReadHandle != INVALID_HANDLE_VALUE) CloseHandle(ReadHandle); ReadHandle = INVALID_HANDLE_VALUE; }; //--------------------------------------------------------------------------- bool TPoKeys55::Connect() { // Ra: to jest do wyczyszcznia z niepotrzebnych zmiennych i komunikatów Close(); GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30}; // wszystkie HID tak mają HDEVINFO DeviceInfoTable; PSP_DEVICE_INTERFACE_DATA InterfaceDataStructure = new SP_DEVICE_INTERFACE_DATA; PSP_DEVICE_INTERFACE_DETAIL_DATA DetailedInterfaceDataStructure = new SP_DEVICE_INTERFACE_DETAIL_DATA; SP_DEVINFO_DATA DevInfoData; DWORD InterfaceIndex = 0; // DWORD StatusLastError=0; DWORD dwRegType; DWORD dwRegSize; DWORD StructureSize = 0; PBYTE PropertyValueBuffer; bool MatchFound; DWORD ErrorStatus; HDEVINFO hDevInfo; std::string DeviceIDFromRegistry; std::string DeviceIDToFind = "Vid_1dc3&Pid_1001&Rev_1000&MI_01"; // First populate a list of plugged in devices (by specifying "DIGCF_PRESENT"), which are of the // specified class GUID. DeviceInfoTable = SetupDiGetClassDevs(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); // Now look through the list we just populated. We are trying to see if any of them match our // device. while (true) { InterfaceDataStructure->cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); if (SetupDiEnumDeviceInterfaces(DeviceInfoTable, NULL, &InterfaceClassGuid, InterfaceIndex, InterfaceDataStructure)) { ErrorStatus = GetLastError(); if (ERROR_NO_MORE_ITEMS == ErrorStatus) // Did we reach the end of the list of matching // devices in the DeviceInfoTable? { // Cound not find the device. Must not have been attached. SetupDiDestroyDeviceInfoList( DeviceInfoTable); // Clean up the old structure we no longer need. // ShowMessage("Erreur: Sortie1"); return false; } } else // Else some other kind of unknown error ocurred... { ErrorStatus = GetLastError(); SetupDiDestroyDeviceInfoList( DeviceInfoTable); // Clean up the old structure we no longer need. // ShowMessage("Erreur: Sortie2"); return false; } // Now retrieve the hardware ID from the registry. The hardware ID contains the VID and PID, // which we will then // check to see if it is the correct device or not. // Initialize an appropriate SP_DEVINFO_DATA structure. We need this structure for // SetupDiGetDeviceRegistryProperty(). DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA); SetupDiEnumDeviceInfo(DeviceInfoTable, InterfaceIndex, &DevInfoData); // First query for the size of the hardware ID, so we can know how big a buffer to allocate // for the data. SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_HARDWAREID, &dwRegType, NULL, 0, &dwRegSize); // Allocate a buffer for the hardware ID. // PropertyValueBuffer=(BYTE*)malloc(dwRegSize); PropertyValueBuffer = new BYTE[dwRegSize]; if (PropertyValueBuffer == NULL) // if null,error,couldn't allocate enough memory { // Can't really recover from this situation,just exit instead. // ShowMessage("Allocation PropertyValueBuffer impossible"); SetupDiDestroyDeviceInfoList( DeviceInfoTable); // Clean up the old structure we no longer need. return false; } // Retrieve the hardware IDs for the current device we are looking at. PropertyValueBuffer // gets filled with a // REG_MULTI_SZ (array of null terminated strings). To find a device,we only care about the // very first string in the // buffer,which will be the "device ID". The device ID is a string which contains the VID // and PID,in the example // format "Vid_04d8&Pid_003f". SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_HARDWAREID, &dwRegType, PropertyValueBuffer, dwRegSize, NULL); // Now check if the first string in the hardware ID matches the device ID of my USB device. // ListBox1->Items->Add((char*)PropertyValueBuffer); DeviceIDFromRegistry = reinterpret_cast(PropertyValueBuffer); // free(PropertyValueBuffer); //No longer need the PropertyValueBuffer,free the memory to // prevent potential memory leaks delete[] PropertyValueBuffer; // No longer need the PropertyValueBuffer,free the memory to // prevent potential memory leaks // Convert both strings to lower case. This makes the code more robust/portable accross OS // Versions DeviceIDFromRegistry = ToLower( DeviceIDFromRegistry ); DeviceIDToFind = ToLower( DeviceIDToFind ); // Now check if the hardware ID we are looking at contains the correct VID/PID MatchFound = ( DeviceIDFromRegistry.find( DeviceIDToFind ) != std::string::npos ); if (MatchFound == true) { // Device must have been found. Open read and write handles. In order to do this,we // will need the actual device path first. // We can get the path by calling SetupDiGetDeviceInterfaceDetail(),however,we have to // call this function twice: The first // time to get the size of the required structure/buffer to hold the detailed interface // data,then a second time to actually // get the structure (after we have allocated enough memory for the structure.) DetailedInterfaceDataStructure->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); // First call populates "StructureSize" with the correct value SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, NULL, NULL, &StructureSize, NULL); DetailedInterfaceDataStructure = (PSP_DEVICE_INTERFACE_DETAIL_DATA)(malloc(StructureSize)); // Allocate enough memory if (DetailedInterfaceDataStructure == NULL) // if null,error,couldn't allocate enough memory { // Can't really recover from this situation,just exit instead. SetupDiDestroyDeviceInfoList( DeviceInfoTable); // Clean up the old structure we no longer need. return false; } DetailedInterfaceDataStructure->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); // Now call SetupDiGetDeviceInterfaceDetail() a second time to receive the goods. SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, DetailedInterfaceDataStructure, StructureSize, NULL, NULL); // We now have the proper device path,and we can finally open read and write handles to // the device. // We store the handles in the global variables "WriteHandle" and "ReadHandle",which we // will use later to actually communicate. WriteHandle = CreateFile((DetailedInterfaceDataStructure->DevicePath), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); ErrorStatus = GetLastError(); // if (ErrorStatus==ERROR_SUCCESS) // ToggleLedBtn->Enabled=true;//Make button no longer greyed out ReadHandle = CreateFile((DetailedInterfaceDataStructure->DevicePath), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); ErrorStatus = GetLastError(); if (ErrorStatus == ERROR_SUCCESS) { // GetPushbuttonState->Enabled=true;//Make button no longer greyed out // StateLabel->Enabled=true;//Make label no longer greyed out } SetupDiDestroyDeviceInfoList( DeviceInfoTable); // Clean up the old structure we no longer need. iRepeats = 0; // nowe szanse na podłączenie return true; } InterfaceIndex++; // Keep looping until we either find a device with matching VID and PID,or until we run out // of items. } // end of while(true) // ShowMessage("Sortie"); return false; } //--------------------------------------------------------------------------- bool TPoKeys55::Write(unsigned char c, unsigned char b3, unsigned char b4, unsigned char b5) { DWORD BytesWritten = 0; OutputBuffer[0] = 0; // The first byte is the "Report ID" and does not get transmitted over the // USB bus. Always set=0. OutputBuffer[1] = 0xBB; // 0xBB - bajt rozpoznawczy dla PoKeys55 OutputBuffer[2] = iLastCommand = c; // operacja: 0x31: blokowy odczyt wejść OutputBuffer[3] = b3; // np. numer pinu (o 1 mniej niż numer na płytce) OutputBuffer[4] = b4; OutputBuffer[5] = b5; OutputBuffer[6] = 0; OutputBuffer[7] = ++cRequest; // numer żądania OutputBuffer[8] = 0; for (int i = 0; i < 8; ++i) OutputBuffer[8] += OutputBuffer[i]; // czy sumować też od 9 do 64? // The basic Windows I/O functions WriteFile() and ReadFile() can be used to read and write to // HID class USB devices //(once we have the read and write handles to the device, which are obtained with CreateFile()). // The following call to WriteFile() sends 64 bytes of data to the USB device. WriteFile(WriteHandle, &OutputBuffer, 65, &BytesWritten, 0); // Blocking function, unless an "overlapped" structure is used return (BytesWritten == 65); // Read(); //odczyt trzeba zrobić inaczej - w tym miejscu będzie za szybko i nic się nie odczyta } //--------------------------------------------------------------------------- bool TPoKeys55::Read() { DWORD BytesRead = 0; InputBuffer[0] = 0; // The first byte is the "Report ID" and does not get transmitted over the // USB bus. Always set=0. // Now get the response packet from the firmware. // The following call to ReadFIle() retrieves 64 bytes of data from the USB device. ReadFile(ReadHandle, &InputBuffer, 65, &BytesRead, 0); // Blocking function,unless an "overlapped" structure is used // InputPacketBuffer[0] is the report ID, which we don't care about. // InputPacketBuffer[1] is an echo back of the command. // InputPacketBuffer[2] contains the I/O port pin value for the pushbutton. return (BytesRead == 65) ? InputBuffer[7] == cRequest : false; } //--------------------------------------------------------------------------- bool TPoKeys55::ReadLoop(int i) { // próbuje odczytać (i) razy do { if (Read()) return true; Sleep(1); // trochę poczekać, aż odpowie } while (--i); return false; } //--------------------------------------------------------------------------- std::string TPoKeys55::Version() { // zwraca numer wersji, funkcja nieoptymalna czasowo (czeka na odpowiedź) if (!WriteHandle) return ""; Write(0x00, 0); // 0x00 - Read serial number, version if (ReadLoop(10)) { // 3: serial MSB; 4: serial LSB; 5: software version (v(1+[4-7]).([0-3])); 6: revision number std::string s = "PoKeys55 #" + std::to_string((InputBuffer[3] << 8) + InputBuffer[4]); s += " v" + std::to_string(1 + (InputBuffer[5] >> 4)) + "." + std::to_string(InputBuffer[5] & 15) + "." + std::to_string(InputBuffer[6]); /* //Ra: pozyskiwanie daty można sobie darować, jest poniekąd bez sensu Write(0x04,0); //0x04 - Read build date: drugi argument zmieniać od 0 do 2, uzyskując kolejno po 4 znaki if (ReadLoop(5)) {//2: 0x04; 3-6: char 1-4, 5-8, 9-11; (83-65-112-32-32-49-32-50-48-49-49-0=="Sep 1 2011") s+=" ("+AnsiString((char*)InputBuffer+3,4); Write(0x04,1); //0x04 - Read build date: drugi argument zmieniać od 0 do 2, uzyskując kolejno po 4 znaki if (ReadLoop(5)) {s+=AnsiString((char*)InputBuffer+3,4); Write(0x04,2); //0x04 - Read build date: drugi argument zmieniać od 0 do 2, uzyskując kolejno po 4 znaki if (ReadLoop(5)) s+=AnsiString((char*)InputBuffer+3,3); } s+=")"; } */ return s; } return ""; }; bool TPoKeys55::PWM(int x, float y) { // ustawienie wskazanego PWM (@12Mhz: 12000=1ms=1000Hz) // iPWM[7]=1024; //1024==85333.3333333333ns=11718.75Hz iPWM[x] = int(0.5f + 0x0FFF * y) & 0x0FFF; // 0x0FFF=4095 return true; } bool TPoKeys55::Update(bool pause) { // funkcja powinna być wywoływana regularnie, np. raz w każdej ramce ekranowej if (pause) { // specjalna procedura, jeśli utracone połączenie spowodowało pauzę iLastCommand = 0; // połączenie zostało na nowo otwarte // iFaza=0; //jeden błąd i podtrzymanie pauzy jest kontynuowane } switch (iFaza) { case 0: // uaktualnienie PWM raz na jakiś czas OutputBuffer[9] = 0x3F; // maska użytych PWM *((int *)(OutputBuffer + 10)) = iPWM[0]; // PWM1 (pin 22) *((int *)(OutputBuffer + 14)) = iPWM[1]; // PWM2 (pin 21) *((int *)(OutputBuffer + 18)) = iPWM[2]; // PWM3 (pin 20) *((int *)(OutputBuffer + 22)) = iPWM[3]; // PWM4 (pin 19) *((int *)(OutputBuffer + 26)) = iPWM[4]; // PWM5 (pin 18) *((int *)(OutputBuffer + 30)) = iPWM[5]; // PWM6 (pin 17) *((int *)(OutputBuffer + 34)) = iPWM[7]; // PWM period if (Write(0xCB, 1)) // wysłanie ustawień (1-ustaw, 0-odczyt) iRepeats = 0; // informacja, że poszło dobrze ++iFaza; // ta faza została zakończona // iRepeats=0; break; case 1: // odczyt wejść analogowych - komenda i przetwarzanie if (iLastCommand != 0x3A) // asynchroniczne ustawienie kontrolki może namieszać Write(0x3A, 0); // 0x3A - Analog inputs reading – all analog inputs in one command else if (Read()) { // jest odebrana ramka i zgodność numeru żądania fAnalog[0] = ((InputBuffer[21] << 8) + InputBuffer[22]) / 4095.0f; // pin 47 fAnalog[1] = ((InputBuffer[19] << 8) + InputBuffer[20]) / 4095.0f; // pin 46 fAnalog[2] = ((InputBuffer[17] << 8) + InputBuffer[18]) / 4095.0f; // pin 45 fAnalog[3] = ((InputBuffer[15] << 8) + InputBuffer[16]) / 4095.0f; // pin 44 fAnalog[4] = ((InputBuffer[13] << 8) + InputBuffer[14]) / 4095.0f; // pin 43 fAnalog[5] = ((InputBuffer[11] << 8) + InputBuffer[12]) / 4095.0f; // pin 42 fAnalog[6] = ((InputBuffer[9] << 8) + InputBuffer[10]) / 4095.0f; // pin 41 ++iFaza; // skoro odczytano, można przejść do kolejnej fazy iRepeats = 0; // zerowanie licznika prób } else ++iRepeats; // licznik nieudanych prób break; case 2: // odczyt wejść cyfrowych - komenda i przetwarzanie if (iLastCommand != 0x31) // asynchroniczne ustawienie kontrolki może namieszać Write(0x31, 0); // 0x31: blokowy odczyt wejść else if (Read()) { // jest odebrana ramka i zgodność numeru żądania iInputs[0] = *((int *)(InputBuffer + 3)); // odczyt 32 bitów iFaza = 3; // skoro odczytano, można kolejny cykl iRepeats = 0; // zerowanie licznika prób } else ++iRepeats; // licznik nieudanych prób break; case 3: // ustawienie wyjść analogowych, 0..4095 mapować na 0..65520 (<<4) if (Write(0x41, 43 - 1, (iPWM[6] >> 4), (iPWM[6] << 4))) // wysłanie ustawień iRepeats = 0; // informacja, że poszło dobrze iFaza = 0; //++iFaza; //ta faza została zakończona // powinno jeszcze przyjść potwierdzenie o kodzie 0x41 break; default: iFaza = 0; // na wypadek, gdyby zbłądziło po jakichś zmianach w kodzie // iRepeats=0; } if (!iRepeats) bNoError = true; // jest OK else if (iRepeats >= 10) // youBy 2014-07: przy 5 powtórzeniach sieje mi pauzą po 2 razy na // sekundę, a przy 10 jest ok { // przekroczenie liczby prób wymusza kolejną fazę ++iFaza; iRepeats = 1; // w nowej fazie nowe szanse, ale nie od 0! bNoError = false; // zgłosić błąd } return (bNoError); // true oznacza prawidłowe działanie // czy w przypadku błędu komunikacji z PoKeys włączać pauzę? // dopiero poprawne podłączenie zeruje licznik prób };