#if defined(USB_CONTROLLER) #include "usbcontroller.h" #ifdef Q_OS_WIN #pragma comment (lib, "Setupapi.lib") #endif #include #include "logcategories.h" usbController::usbController() { // As this is run in it's own thread, don't do anything in the constructor qInfo(logUsbControl()) << "Starting usbController()"; } usbController::~usbController() { qInfo(logUsbControl) << "Ending usbController()"; if (handle) { programOverlay(60, "Goodbye from wfview"); if (usbDevice == RC28) { ledControl(false, 3); } hid_close(handle); } hid_exit(); #if (QT_VERSION < QT_VERSION_CHECK(6,0,0)) if (gamepad != Q_NULLPTR) { delete gamepad; gamepad = Q_NULLPTR; } #endif } void usbController::init(int sens, QMutex* mut) { this->mutex = mut; this->sensitivity = sens; emit sendSensitivity(sensitivity); #ifdef HID_API_VERSION_MAJOR if (HID_API_VERSION == HID_API_MAKE_VERSION(hid_version()->major, hid_version()->minor, hid_version()->patch)) { qInfo(logUsbControl) << QString("Compile-time version matches runtime version of hidapi: %0.%1.%2") .arg(hid_version()->major) .arg(hid_version()->minor) .arg(hid_version()->patch); } else { qInfo(logUsbControl) << QString("Compile-time and runtime versions of hidapi do not match (%0.%1.%2 vs %0.%1.%2)") .arg(HID_API_VERSION_MAJOR) .arg(HID_API_VERSION_MINOR) .arg(HID_API_VERSION_PATCH) .arg(hid_version()->major) .arg(hid_version()->minor) .arg(hid_version()->patch); } #endif hidStatus = hid_init(); if (hidStatus) { qInfo(logUsbControl()) << "Failed to intialize HID Devices"; } else { #ifdef HID_API_VERSION_MAJOR #if defined(__APPLE__) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0) hid_darwin_set_open_exclusive(0); #endif #endif qInfo(logUsbControl()) << "Found available HID devices (not all will be suitable for use):"; struct hid_device_info* devs; devs = hid_enumerate(0x0, 0x0); while (devs) { qInfo(logUsbControl()) << QString("Device found: (%0:%1) %2 manufacturer: (%3)%4 usage: 0x%5 usage_page 0x%6") .arg(devs->vendor_id, 4, 16, QChar('0')) .arg(devs->product_id, 4, 16, QChar('0')) .arg(QString::fromWCharArray(devs->product_string)) .arg(QString::fromWCharArray(devs->product_string)) .arg(QString::fromWCharArray(devs->manufacturer_string)) .arg(devs->usage, 4, 16, QChar('0')) .arg(devs->usage_page, 4, 16, QChar('0')); devs = devs->next; } hid_free_enumeration(devs); } } /* run() is called every 2s and attempts to connect to a supported controller */ void usbController::run() { if (commands == Q_NULLPTR || hidStatus) { // We are not ready yet, commands haven't been loaded or hid hasn't been initialized! QTimer::singleShot(1000, this, SLOT(run())); return; } #if (QT_VERSION < QT_VERSION_CHECK(6,0,0)) if (gamepad == Q_NULLPTR) { auto gamepads = QGamepadManager::instance()->connectedGamepads(); if (!gamepads.isEmpty()) { qInfo(logUsbControl()) << "Found" << gamepads.size() << "Gamepad controllers"; // If we got here, we have detected a gamepad of some description! gamepad = new QGamepad(*gamepads.begin(), this); qInfo(logUsbControl()) << "Gamepad 0 is " << gamepad->name(); if (gamepad->name() == "Microsoft X-Box 360 pad 0") { usbDevice = xBoxGamepad; } else { usbDevice = unknownGamepad; } connect(gamepad, &QGamepad::buttonDownChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button Down" << pressed; this->buttonState("DOWN", pressed); }); connect(gamepad, &QGamepad::buttonUpChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button Up" << pressed; this->buttonState("UP", pressed); }); connect(gamepad, &QGamepad::buttonLeftChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button Left" << pressed; this->buttonState("LEFT", pressed); }); connect(gamepad, &QGamepad::buttonRightChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button Right" << pressed; this->buttonState("RIGHT", pressed); }); connect(gamepad, &QGamepad::buttonCenterChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button Center" << pressed; this->buttonState("CENTER", pressed); }); connect(gamepad, &QGamepad::axisLeftXChanged, this, [this](double value) { qInfo(logUsbControl()) << "Left X" << value; this->buttonState("LEFTX", value); }); connect(gamepad, &QGamepad::axisLeftYChanged, this, [this](double value) { qInfo(logUsbControl()) << "Left Y" << value; this->buttonState("LEFTY", value); }); connect(gamepad, &QGamepad::axisRightXChanged, this, [this](double value) { qInfo(logUsbControl()) << "Right X" << value; this->buttonState("RIGHTX", value); }); connect(gamepad, &QGamepad::axisRightYChanged, this, [this](double value) { qInfo(logUsbControl()) << "Right Y" << value; this->buttonState("RIGHTY", value); }); connect(gamepad, &QGamepad::buttonAChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button A" << pressed; this->buttonState("A", pressed); }); connect(gamepad, &QGamepad::buttonBChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button B" << pressed; this->buttonState("B", pressed); }); connect(gamepad, &QGamepad::buttonXChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button X" << pressed; this->buttonState("X", pressed); }); connect(gamepad, &QGamepad::buttonYChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button Y" << pressed; this->buttonState("Y", pressed); }); connect(gamepad, &QGamepad::buttonL1Changed, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button L1" << pressed; this->buttonState("L1", pressed); }); connect(gamepad, &QGamepad::buttonR1Changed, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button R1" << pressed; this->buttonState("R1", pressed); }); connect(gamepad, &QGamepad::buttonL2Changed, this, [this](double value) { qInfo(logUsbControl()) << "Button L2: " << value; this->buttonState("L2", value); }); connect(gamepad, &QGamepad::buttonR2Changed, this, [this](double value) { qInfo(logUsbControl()) << "Button R2: " << value; this->buttonState("R2", value); }); connect(gamepad, &QGamepad::buttonSelectChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button Select" << pressed; this->buttonState("SELECT", pressed); }); connect(gamepad, &QGamepad::buttonStartChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button Start" << pressed; this->buttonState("START", pressed); }); connect(gamepad, &QGamepad::buttonGuideChanged, this, [this](bool pressed) { qInfo(logUsbControl()) << "Button Guide" << pressed; }); emit newDevice(usbDevice, buttonList, knobList, commands, mutex); // Let the UI know we have a new controller return; } } else if (!gamepad->isConnected()) { delete gamepad; gamepad = Q_NULLPTR; } #endif struct hid_device_info* devs; devs = hid_enumerate(0x0, 0x0); usbDeviceType tempDev = usbDevice; this->path = ""; // Always only look for the first device and then exit // Maybe in the future we could add support for multiple devices? while (devs) { for (int i = 0; i < (int)sizeof knownUsbDevices / (int)sizeof knownUsbDevices[0]; i++) { if (devs->vendor_id == knownUsbDevices[i][1] && devs->product_id == knownUsbDevices[i][2] && (knownUsbDevices[i][3] == 0x00 || devs->usage == knownUsbDevices[i][3]) && (knownUsbDevices[i][4] == 0x00 || devs->usage_page == knownUsbDevices[i][4])) { this->manufacturer = QString::fromWCharArray(devs->manufacturer_string); this->vendorId = devs->vendor_id; this->productId = devs->product_id; this->product = QString::fromWCharArray(devs->product_string); this->serial = QString::fromWCharArray(devs->serial_number); this->path = QString::fromLocal8Bit(devs->path); this->deviceId = QString("0x%1").arg(this->productId, 4, 16, QChar('0')); this->usbDevice = (usbDeviceType)knownUsbDevices[i][0]; break; } } devs = devs->next; } hid_free_enumeration(devs); if (!this->path.isEmpty()) { qInfo(logUsbControl()) << QString("Attempting to connect to %0").arg(this->product); this->handle = hid_open_path(this->path.toLocal8Bit()); } if (this->handle) { qInfo(logUsbControl()) << QString("Connected to device: %0 from %1 S/N %2").arg(this->product).arg(this->manufacturer).arg(this->serial); hid_set_nonblocking(this->handle, 1); // Set RC28 LEDs to default state if (usbDevice == RC28) { getVersion(); ledControl(false, 0); ledControl(false, 1); ledControl(false, 2); ledControl(true, 3); } else if (usbDevice == eCoderPlus) { knobValues.clear(); knobSend.clear(); knobValues.append({ 0,0,0 }); knobSend.append({ 0,0,0 }); } else if (usbDevice == QuickKeys) { // Subscribe to event streams QByteArray b(32,0x0); b[0] = (qint8)0x02; b[1] = (qint8)0xb0; b[2] = (qint8)0x04; b.replace(10, sizeof(this->deviceId), this->deviceId.toLocal8Bit()); hid_write(this->handle, (const unsigned char*)b.constData(), b.size()); b[0] = (qint8)0x02; b[1] = (qint8)0xb4; b[2] = (qint8)0x10; b.replace(10, sizeof(this->deviceId), this->deviceId.toLocal8Bit()); hid_write(this->handle, (const unsigned char*)b.constData(), b.size()); } // Let the UI know we have a new controller emit newDevice(usbDevice, buttonList, knobList, commands, mutex); // Run the periodic timer to get data QTimer::singleShot(25, this, SLOT(runTimer())); } else { // This should only get displayed once if we fail to connect to a device if (usbDevice != usbNone && tempDev != usbDevice) { qInfo(logUsbControl()) << QString("Error connecting to %0: %1") .arg(this->product) .arg(QString::fromWCharArray(hid_error(this->handle))); } // Call me again in 2 seconds to try connecting again QTimer::singleShot(2000, this, SLOT(run())); } } /* runTimer is called every 25ms once a connection to a supported controller is established */ void usbController::runTimer() { int res=1; if (!this->handle) { // Something bad happened! QTimer::singleShot(25, this, SLOT(runTimer())); res = 0; } QMutexLocker locker(mutex); while (res > 0) { QByteArray data(HIDDATALENGTH, 0x0); res = hid_read(this->handle, (unsigned char*)data.data(), HIDDATALENGTH); if (res < 0) { qInfo(logUsbControl()) << "USB Device disconnected" << this->product; emit newDevice(0, buttonList, knobList, commands, mutex); this->product = ""; this->manufacturer = ""; this->serial = ""; hid_close(this->handle); this->handle = NULL; QTimer::singleShot(1000, this, SLOT(run())); return; } else if (res == 5 && (usbDevice == shuttleXpress || usbDevice == shuttlePro2)) { data.resize(res); /*qDebug(logUsbControl()) << "usbController Data received " << hex << (unsigned char)data[0] << ":" << hex << (unsigned char)data[1] << ":" << hex << (unsigned char)data[2] << ":" << hex << (unsigned char)data[3] << ":" << hex << (unsigned char)data[4]; */ quint32 tempButtons = ((quint8)data[4] << 8) | ((quint8)data[3] & 0xff); unsigned char tempJogpos = (unsigned char)data[1]; unsigned char tempShutpos = (unsigned char)data[0]; if (tempJogpos == jogpos + 1 || (tempJogpos == 0 && jogpos == 0xff)) { jogCounter++; //qDebug(logUsbControl()) << "JOG PLUS" << jogCounter; } else if (tempJogpos != jogpos) { jogCounter--; //qDebug(logUsbControl()) << "JOG MINUS" << jogCounter; } /* Button matrix: 1000000000000000 = button15 0100000000000000 = button14 0010000000000000 = button13 0001000000000000 = button12 0000100000000000 = button11 0000010000000000 = button10 0000001000000000 = button9 0000000100000000 = button8 - xpress0 0000000010000000 = button7 - xpress1 0000000001000000 = button6 - xpress2 0000000000100000 = button5 - xpress3 0000000000010000 = button4 - xpress4 0000000000001000 = button3 0000000000000100 = button2 0000000000000010 = button1 0000000000000001 = button0 */ if (buttons != tempButtons) { // Step through all buttons and emit ones that have been pressed. for (unsigned char i = 0; i < 16; i++) { auto but = std::find_if(buttonList->begin(), buttonList->end(), [this, i](const BUTTON& b) { return (b.dev == this->usbDevice && b.num == i); }); if (but != buttonList->end()) { if ((tempButtons >> i & 1) && !(buttons >> i & 1)) { qDebug(logUsbControl()) << "On Button event:" << but->onCommand->text; emit button(but->onCommand); } else if ((buttons >> i & 1) && !(tempButtons >> i & 1)) { qDebug(logUsbControl()) << "Off Button event:" << but->offCommand->text; emit button(but->offCommand); } } } } buttons = tempButtons; jogpos = tempJogpos; shutpos = tempShutpos; } else if ((res > 31) && usbDevice == RC28) { // This is a response from the Icom RC28 if ((unsigned char)data[0] == 0x02) { qInfo(logUsbControl()) << QString("Received RC-28 Firmware Version: %0").arg(QString(data.mid(1,data.indexOf(" ")-1))); } else { data.resize(8); // Buttons BUTTON* butptt = Q_NULLPTR; BUTTON* butf1 = Q_NULLPTR; BUTTON* butf2 = Q_NULLPTR;; for (BUTTON* but = buttonList->begin(); but != buttonList->end(); but++) { if (but->dev == usbDevice) { if (but->num == 0) { butptt = but; } else if (but->num == 1) { butf1 = but; } else if (but->num == 2) { butf2 = but; } } } if (butptt != Q_NULLPTR && !((unsigned char)data[5] ^ 0x06) && ((unsigned char)lastData[5] ^ 0x06)) { // TRANSMIT key down only (no other keys down) qDebug(logUsbControl()) << "PTT key down"; qInfo(logUsbControl()) << "On Button event:" << butptt->onCommand->text; ledControl(true, 0); emit button(butptt->onCommand); } else if (butptt != Q_NULLPTR && ((unsigned char)data[5] ^ 0x06) && !((unsigned char)lastData[5] ^ 0x06)) { // TRANSMIT key up only (no other keys down) //emit button(false, 6); qDebug(logUsbControl()) << "PTT key up"; qInfo(logUsbControl()) << "Off Button event:" << butptt->offCommand->text; ledControl(false, 0); emit button(butptt->offCommand); } if (butf1 != Q_NULLPTR && !((unsigned char)data[5] ^ 0x05) && ((unsigned char)lastData[5] ^ 0x05)) { // F-1 key up only (no other keys down) //emit button(true, 5); qDebug(logUsbControl()) << "F-1 key down"; qInfo(logUsbControl()) << "On Button event:" << butf1->onCommand->text; ledControl(true, 1); emit button(butf1->onCommand); } else if (butf1 != Q_NULLPTR && ((unsigned char)data[5] ^ 0x05) && !((unsigned char)lastData[5] ^ 0x05)) { // F-1 key down only (no other keys down) //emit button(false, 5); qDebug(logUsbControl()) << "F-1 key up"; qInfo(logUsbControl()) << "Off Button event:" << butf1->offCommand->text; ledControl(false, 1); emit button(butf1->offCommand); } if (butf2 != Q_NULLPTR && !((unsigned char)data[5] ^ 0x03) && ((unsigned char)lastData[5] ^ 0x03)) { // F-2 key up only (no other keys down) //emit button(true, 7); qDebug(logUsbControl()) << "F-2 key down"; qInfo(logUsbControl()) << "On Button event:" << butf2->onCommand->text; ledControl(true, 2); emit button(butf2->onCommand); } else if (butf2 != Q_NULLPTR && ((unsigned char)data[5] ^ 0x03) && !((unsigned char)lastData[5] ^ 0x03)) { // F-2 key down only (no other keys down) //emit button(false, 7); qDebug(logUsbControl()) << "F-2 key up"; qInfo(logUsbControl()) << "Off Button event:" << butf2->offCommand->text; ledControl(false, 2); emit button(butf2->offCommand); } if ((unsigned char)data[5] == 0x07) { if ((unsigned char)data[3] == 0x01) { jogCounter = jogCounter + data[1]; } else if ((unsigned char)data[3] == 0x02) { jogCounter = jogCounter - data[1]; } } lastData = data; } } else if (usbDevice == eCoderPlus && data.length() > 0x0f && (quint8)data[0] == 0xff) { /* Button matrix: DATA3 DATA2 DATA 1 765432107654321076543210 001000000000000000000000 = Left CW 000100000000000000000000 = Right CW 000010000000000000000000 = PTT 000001000000000000000000 = Knob 3 Press 000000100000000000000000 = Knob 2 Press 000000010000000000000000 = Knob 1 Press 000000000100000000000000 = button14 000000000010000000000000 = button13 000000000001000000000000 = button12 000000000000100000000000 = button11 000000000000010000000000 = button10 000000000000001000000000 = button9 000000000000000100000000 = button8 000000000000000010000000 = button7 000000000000000001000000 = button6 000000000000000000100000 = button5 000000000000000000010000 = button4 000000000000000000001000 = button3 000000000000000000000100 = button2 000000000000000000000010 = button1 */ quint32 tempButtons = ((quint8)data[3] << 16) | ((quint8)data[2] << 8) | ((quint8)data[1] & 0xff); quint32 tempKnobs = ((quint8)data[16] << 16) | ((quint8)data[15] << 8) | ((quint8)data[14] & 0xff); if (buttons != tempButtons) { // Step through all buttons and emit ones that have been pressed. for (unsigned char i = 1; i < 23; i++) { auto but = std::find_if(buttonList->begin(), buttonList->end(), [this, i](const BUTTON& b) { return (b.dev == this->usbDevice && b.num == i); }); if (but != buttonList->end()) { if ((tempButtons >> i & 1) && !(buttons >> i & 1)) { qDebug(logUsbControl()) << "On Button event:" << but->onCommand->text; emit button(but->onCommand); } else if ((buttons >> i & 1) && !(tempButtons >> i & 1)) { qDebug(logUsbControl()) << "Off Button event:" << but->offCommand->text; emit button(but->offCommand); } } } } buttons = tempButtons; if (knobs != tempKnobs) { // One of the knobs has moved for (unsigned char i = 0; i < 3; i++) { if ((tempKnobs >> (i * 8) & 0xff) != (knobs >> (i * 8) & 0xff)) { knobValues[i] = knobValues[i] + (qint8)((knobs >> (i * 8)) & 0xff); } } } knobs = tempKnobs; // Tuning knob jogCounter = jogCounter + (qint8)data[13]; } else if (usbDevice == QuickKeys && (quint8)data[0] == 0x02) { if ((quint8)data[1] == 0xf0) { //qInfo(logUsbControl()) << "Received:" << data; quint32 tempButtons = (data[3] << 8) | (data[2] & 0xff); // Step through all buttons and emit ones that have been pressed. for (unsigned char i = 0; i < 10; i++) { auto but = std::find_if(buttonList->begin(), buttonList->end(), [this, i](const BUTTON& b) { return (b.dev == this->usbDevice && b.num == i); }); if (but != buttonList->end()) { if ((tempButtons >> i & 1) && !(buttons >> i & 1)) { qDebug(logUsbControl()) << "On Button event:" << but->onCommand->text; emit button(but->onCommand); programButton(but->num, but->offCommand->text); } else if ((buttons >> i & 1) && !(tempButtons >> i & 1)) { qDebug(logUsbControl()) << "Off Button event:" << but->offCommand->text; emit button(but->offCommand); programButton(but->num, but->onCommand->text); } } } buttons = tempButtons; // Tuning knob if (data[7] & 0x01) { jogCounter++; } else if (data[7] & 0x02) { jogCounter--; } } else if ((quint8)data[1] == 0xf2 && (quint8)data[2] == 0x01) { // Battery level quint8 battery = (quint8)data[3]; qDebug(logUsbControl()) << QString("Battery level %1 %").arg(battery); } } if (lastusbController.msecsTo(QTime::currentTime()) >= 100 || lastusbController > QTime::currentTime()) { if (usbDevice == shuttleXpress || usbDevice == shuttlePro2) { if (shutpos > 0 && shutpos < 0x08) { shutMult = shutpos; emit doShuttle(true, shutMult); qDebug(logUsbControl()) << "Shuttle PLUS" << shutMult; } else if (shutpos > 0xEF) { shutMult = abs(shutpos - 0xff) + 1; emit doShuttle(false, shutMult); qDebug(logUsbControl()) << "Shuttle MINUS" << shutMult; } } if (usbDevice == eCoderPlus) { for (unsigned char i = 0; i < 3; i++) { for (KNOB* kb = knobList->begin(); kb != knobList->end(); kb++) { if (kb && kb->dev == usbDevice && kb->num == i + 1 && knobValues[i]) { COMMAND cmd; cmd.command = kb->command->command; if (knobSend[i] + (knobValues[i] * 10) <= 0) { knobSend[i] = 0; } else if (knobSend[i] + (knobValues[i] * 10) >= 255) { knobSend[i] = 255; } else { knobSend[i] = knobSend[i] + (knobValues[i] * 10); } cmd.suffix = knobSend[i]; qInfo(logUsbControl()) << "Sending Knob:" << kb->num << "Command:" << cmd.command << " Value:" << cmd.suffix << "Raw value:" << knobValues[i]; emit button(&cmd); knobValues[i] = 0; } } } } if (jogCounter != 0) { emit sendJog(jogCounter/sensitivity); qDebug(logUsbControl()) << "Change Frequency by" << jogCounter << "hz"; jogCounter = 0; } lastusbController = QTime::currentTime(); } } // Run every 25ms QTimer::singleShot(25, this, SLOT(runTimer())); } /* Functions below receive various settings from other classes/threads */ void usbController::receiveCommands(QVector* cmds) { qDebug(logUsbControl()) << "Receiving commands"; commands = cmds; } void usbController::receiveButtons(QVector