-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
HID functions to read back the status of an HID controller (like MIDI SYSEX) #3317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4745d40
a9203e3
57270d4
ec00deb
c65ea6b
fe3dce6
e3e6c62
77fa9b0
aeaffe2
a1c2a73
1015854
66feefd
37112d6
f3668bb
6d5e1bc
804bcff
722ced8
bad9b11
5ab9726
16059eb
0a00939
ff1a1cd
17cad5f
8263641
d614250
19be7ff
ba53b75
79e1392
706ea80
fb7d30d
cfccc4c
54a1f05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ HidController::HidController( | |
| mixxx::hid::DeviceInfo&& deviceInfo) | ||
| : m_deviceInfo(std::move(deviceInfo)), | ||
| m_pHidDevice(nullptr), | ||
| m_iPollingBufferIndex(0) { | ||
| m_pollingBufferIndex(0) { | ||
| setDeviceCategory(mixxx::hid::DeviceCategory::guessFromDeviceInfo(m_deviceInfo)); | ||
| setDeviceName(m_deviceInfo.formatName()); | ||
|
|
||
|
|
@@ -109,7 +109,7 @@ int HidController::open() { | |
| for (int i = 0; i < kNumBuffers; i++) { | ||
| memset(m_pPollData[i], 0, kBufferSize); | ||
| } | ||
| m_iLastPollSize = 0; | ||
| m_lastPollSize = 0; | ||
|
|
||
| setOpen(true); | ||
| startEngine(); | ||
|
|
@@ -136,6 +136,67 @@ int HidController::close() { | |
| return 0; | ||
| } | ||
|
|
||
| void HidController::processInputReport(int bytesRead) { | ||
| Trace process("HidController processInputReport"); | ||
| unsigned char* pPreviousBuffer = m_pPollData[(m_pollingBufferIndex + 1) % kNumBuffers]; | ||
| unsigned char* pCurrentBuffer = m_pPollData[m_pollingBufferIndex]; | ||
| // Some controllers such as the Gemini GMX continuously send input reports even if it | ||
| // is identical to the previous send input report. If this loop processed all those redundant | ||
| // input report, it would be a big performance problem to run JS code for every input report and | ||
| // would be unnecessary. | ||
| // This assumes that the redundant input report all use the same report ID. In practice we | ||
| // have not encountered any controllers that send redundant input report with different report | ||
| // IDs. If any such devices exist, this may be changed to use a separate buffer to store | ||
| // the last input report for each report ID. | ||
|
JoergAtGithub marked this conversation as resolved.
|
||
| if (bytesRead == m_lastPollSize && | ||
| memcmp(pCurrentBuffer, pPreviousBuffer, bytesRead) == 0) { | ||
| return; | ||
| } | ||
| // Cycle between buffers so the memcmp above does not require deep copying to another buffer. | ||
| m_pollingBufferIndex = (m_pollingBufferIndex + 1) % kNumBuffers; | ||
| m_lastPollSize = bytesRead; | ||
| auto incomingData = QByteArray::fromRawData( | ||
| reinterpret_cast<char*>(pCurrentBuffer), bytesRead); | ||
|
|
||
| // Execute callback function in JavaScript mapping | ||
| // and print to stdout in case of --controllerDebug | ||
| receive(incomingData, mixxx::Time::elapsed()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The time should be measured as early as possible to a void a jitter do to suspends in the code before. The best would be to ask hidapi for it. But it looks like that is not implemented, is it?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By the way, we have no prefix rule for i.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the prefix i ! |
||
| } | ||
|
|
||
| QList<int> HidController::getInputReport(unsigned int reportID) { | ||
| Trace hidRead("HidController getInputReport"); | ||
| int bytesRead; | ||
|
|
||
| m_pPollData[m_pollingBufferIndex][0] = reportID; | ||
| bytesRead = hid_get_input_report(m_pHidDevice, m_pPollData[m_pollingBufferIndex], kBufferSize); | ||
|
|
||
| controllerDebug(bytesRead | ||
| << "bytes received by hid_get_input_report" << getName() | ||
| << "serial #" << m_deviceInfo.serialNumber() | ||
| << "(including one byte for the report ID:" | ||
| << QString::number(static_cast<quint8>(reportID), 16) | ||
| .toUpper() | ||
| .rightJustified(2, QChar('0')) | ||
| << ")"); | ||
|
|
||
| if (bytesRead <= kReportIdSize) { | ||
| // -1 is the only error value according to hidapi documentation. | ||
| // Otherwise minimum possible value is 1, because 1 byte is for the reportID, | ||
| // the smallest report with data is therefore 2 bytes. | ||
| DEBUG_ASSERT(bytesRead <= kReportIdSize); | ||
| return QList<int>(); | ||
| } | ||
|
|
||
| // Convert array of bytes read in a JavaScript compatible return type | ||
| // For compatibilty with the array provided by HidController::poll the reportID is contained as prefix | ||
| QList<int> dataList; | ||
|
JoergAtGithub marked this conversation as resolved.
|
||
| dataList.reserve(bytesRead); | ||
| for (int i = 0; i < bytesRead; i++) { | ||
| dataList.append(m_pPollData[m_pollingBufferIndex][i]); | ||
| } | ||
| return dataList; | ||
| } | ||
|
|
||
| bool HidController::poll() { | ||
| Trace hidRead("HidController poll"); | ||
|
|
||
|
|
@@ -145,38 +206,16 @@ bool HidController::poll() { | |
| // There is no safety net for this because it has not been demonstrated to be | ||
| // a problem in practice. | ||
| while (true) { | ||
| // Cycle between buffers so the memcmp below does not require deep copying to another buffer. | ||
| unsigned char* pPreviousBuffer = m_pPollData[m_iPollingBufferIndex]; | ||
| const int currentBufferIndex = (m_iPollingBufferIndex + 1) % kNumBuffers; | ||
| unsigned char* pCurrentBuffer = m_pPollData[currentBufferIndex]; | ||
|
|
||
| int bytesRead = hid_read(m_pHidDevice, pCurrentBuffer, kBufferSize); | ||
| int bytesRead = hid_read(m_pHidDevice, m_pPollData[m_pollingBufferIndex], kBufferSize); | ||
| if (bytesRead < 0) { | ||
| // -1 is the only error value according to hidapi documentation. | ||
| DEBUG_ASSERT(bytesRead == -1); | ||
| return false; | ||
| } else if (bytesRead == 0) { | ||
| // No packet was available to be read | ||
| return true; | ||
| } | ||
|
|
||
| Trace process("HidController process packet"); | ||
| // Some controllers such as the Gemini GMX continuously send input packets even if it | ||
| // is identical to the previous packet. If this loop processed all those redundant | ||
| // packets, it would be a big performance problem to run JS code for every packet and | ||
| // would be unnecessary. | ||
| // This assumes that the redundant packets all use the same report ID. In practice we | ||
| // have not encountered any controllers that send redundant packets with different report | ||
| // IDs. If any such devices exist, this may be changed to use a separate buffer to store | ||
| // the last packet for each report ID. | ||
| if (bytesRead == m_iLastPollSize && | ||
| memcmp(pCurrentBuffer, pPreviousBuffer, bytesRead) == 0) { | ||
| continue; | ||
| } | ||
| m_iLastPollSize = bytesRead; | ||
| m_iPollingBufferIndex = currentBufferIndex; | ||
| auto incomingData = QByteArray::fromRawData( | ||
| reinterpret_cast<char*>(pCurrentBuffer), bytesRead); | ||
| receive(incomingData, mixxx::Time::elapsed()); | ||
| processInputReport(bytesRead); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -254,3 +293,42 @@ void HidController::sendFeatureReport( | |
| ControllerJSProxy* HidController::jsProxy() { | ||
| return new HidControllerJSProxy(this); | ||
| } | ||
|
|
||
| QList<int> HidController::getFeatureReport( | ||
| unsigned int reportID) { | ||
| unsigned char dataRead[kReportIdSize + kBufferSize]; | ||
|
Be-ing marked this conversation as resolved.
|
||
| dataRead[0] = reportID; | ||
|
|
||
| int bytesRead; | ||
| bytesRead = hid_get_feature_report(m_pHidDevice, | ||
| dataRead, | ||
| kReportIdSize + kBufferSize); | ||
| if (bytesRead <= kReportIdSize) { | ||
| // -1 is the only error value according to hidapi documentation. | ||
| // Otherwise minimum possible value is 1, because 1 byte is for the reportID, | ||
| // the smallest report with data is therefore 2 bytes. | ||
| qWarning() << "getFeatureReport is unable to get data from" << getName() | ||
| << "serial #" << m_deviceInfo.serialNumber() << ":" | ||
| << mixxx::convertWCStringToQString( | ||
| hid_error(m_pHidDevice), | ||
| kMaxHidErrorMessageSize); | ||
| } else { | ||
| controllerDebug(bytesRead | ||
| << "bytes received by getFeatureReport from" << getName() | ||
| << "serial #" << m_deviceInfo.serialNumber() | ||
| << "(including one byte for the report ID:" | ||
| << QString::number(static_cast<quint8>(reportID), 16) | ||
| .toUpper() | ||
| .rightJustified(2, QChar('0')) | ||
| << ")") | ||
| } | ||
|
|
||
| // Convert array of bytes read in a JavaScript compatible return type | ||
| // For compatibilty with input array HidController::sendFeatureReport, a reportID prefix is not added here | ||
| QList<int> dataList; | ||
| dataList.reserve(bytesRead - kReportIdSize); | ||
| for (int i = kReportIdSize; i < bytesRead; i++) { | ||
| dataList.append(dataRead[i]); | ||
| } | ||
| return dataList; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.