From a1c130858029406e2b3c4e93d6ea6922c79e1ce1 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Sun, 23 Apr 2023 19:52:02 +0100 Subject: [PATCH 1/7] Rotator Controller: Add gamepad input controller support. --- CMakeLists.txt | 4 +- .../feature/gs232controller/CMakeLists.txt | 10 + .../gamepadinputcontroller.cpp | 139 +++++ .../gs232controller/gamepadinputcontroller.h | 53 ++ .../gs232controller/gs232controller.cpp | 14 + .../gs232controller/gs232controllergui.cpp | 146 +++++- .../gs232controller/gs232controllergui.h | 14 + .../gs232controller/gs232controllergui.ui | 476 ++++++++++-------- .../gs232controllersettings.cpp | 19 +- .../gs232controller/gs232controllersettings.h | 2 + .../gs232controller/inputcontroller.cpp | 63 +++ .../feature/gs232controller/inputcontroller.h | 58 +++ plugins/feature/gs232controller/readme.md | 18 +- .../api/swagger/include/GS232Controller.yaml | 7 + .../qt5/client/SWGGS232ControllerSettings.cpp | 48 ++ .../qt5/client/SWGGS232ControllerSettings.h | 12 + 16 files changed, 874 insertions(+), 209 deletions(-) create mode 100644 plugins/feature/gs232controller/gamepadinputcontroller.cpp create mode 100644 plugins/feature/gs232controller/gamepadinputcontroller.h create mode 100644 plugins/feature/gs232controller/inputcontroller.cpp create mode 100644 plugins/feature/gs232controller/inputcontroller.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dcfc67c2b..61b3c54ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -610,7 +610,9 @@ else() MultimediaWidgets Positioning Charts - SerialPort) + SerialPort + OPTIONAL_COMPONENTS + Gamepad) endif() # for the server we don't need OpenGL/Qt Quick components diff --git a/plugins/feature/gs232controller/CMakeLists.txt b/plugins/feature/gs232controller/CMakeLists.txt index cfab468b1..2d3a24fd4 100644 --- a/plugins/feature/gs232controller/CMakeLists.txt +++ b/plugins/feature/gs232controller/CMakeLists.txt @@ -38,17 +38,27 @@ if(NOT SERVER_MODE) gs232controllergui.ui dfmstatusdialog.cpp dfmstatusdialog.ui + inputcontroller.cpp ) set(gs232controller_HEADERS ${gs232controller_HEADERS} gs232controllergui.h dfmstatusdialog.h + inputcontroller.h ) set(TARGET_NAME featuregs232controller) set(TARGET_LIB Qt::Widgets) set(TARGET_LIB_GUI "sdrgui") set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) + + if(Qt${QT_DEFAULT_MAJOR_VERSION}Gamepad_FOUND) + add_compile_definitions(QT_GAMEPAD_FOUND) + set(TARGET_LIB ${TARGET_LIB} Qt::Gamepad) + set(gs232controller_SOURCES ${gs232controller_SOURCES} gamepadinputcontroller.cpp) + set(gs232controller_HEADERS ${gs232controller_HEADERS} gamepadinputcontroller.h) + endif() + else() set(TARGET_NAME featuregs232controllersrv) set(TARGET_LIB "") diff --git a/plugins/feature/gs232controller/gamepadinputcontroller.cpp b/plugins/feature/gs232controller/gamepadinputcontroller.cpp new file mode 100644 index 000000000..7dc8c78d9 --- /dev/null +++ b/plugins/feature/gs232controller/gamepadinputcontroller.cpp @@ -0,0 +1,139 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "gamepadinputcontroller.h" + +GamepadInputController::GamepadInputController(int deviceId) : + m_gamepad(deviceId), + m_rightX(0.0), + m_rightY(0.0), + m_leftX(0.0), + m_leftY(0.0) +{ + connect(&m_gamepad, &QGamepad::axisRightXChanged, this, &GamepadInputController::axisRightXChanged); + connect(&m_gamepad, &QGamepad::axisRightYChanged, this, &GamepadInputController::axisRightYChanged); + connect(&m_gamepad, &QGamepad::axisLeftXChanged, this, &GamepadInputController::axisLeftXChanged); + connect(&m_gamepad, &QGamepad::axisLeftYChanged, this, &GamepadInputController::axisLeftYChanged); +} + +double GamepadInputController::getAxisValue(int axis) +{ + switch (axis) + { + case 0: + return m_rightX; + case 1: + return m_rightY; + case 2: + return m_leftX; + case 3: + return m_leftY; + } + return 0.0; +} + +int GamepadInputController::getNumberOfAxes() const +{ + return 4; +} + +void GamepadInputController::axisRightXChanged(double value) +{ + m_rightX = value; +} + +void GamepadInputController::axisRightYChanged(double value) +{ + m_rightY = value; +} + +void GamepadInputController::axisLeftXChanged(double value) +{ + m_leftX = value; +} + +void GamepadInputController::axisLeftYChanged(double value) +{ + m_leftY = value; +} + +QStringList GamepadInputController::getAllControllers() +{ + QStringList names; + QGamepadManager *gamepadManager = QGamepadManager::instance(); + + if (gamepadManager) + { + const QList gamepads = gamepadManager->connectedGamepads(); + for (const auto gamepad : gamepads) + { + QString name; + if (gamepadManager->gamepadName(gamepad).isEmpty()) { + name = QString("Gamepad %1").arg(gamepad); + } else { + name = gamepadManager->gamepadName(gamepad); + } + qDebug() << "GamepadInputController::getAllControllers: Gamepad: " << gamepad << "name:" << gamepadManager->gamepadName(gamepad) << " connected " << gamepadManager->isGamepadConnected(gamepad); + names.append(name); + } + if (gamepads.size() == 0) { + qDebug() << "GamepadInputController::getAllControllers: No gamepads"; + } + } + else + { + qDebug() << "GamepadInputController::getAllControllers: No gamepad manager"; + } + return names; +} + +GamepadInputController* GamepadInputController::open(const QString& name) +{ + GamepadInputController *inputController = nullptr; + QGamepadManager *gamepadManager = QGamepadManager::instance(); + + if (gamepadManager) + { + const QList gamepads = gamepadManager->connectedGamepads(); + for (const auto gamepad : gamepads) + { + QString gamepadName; + if (gamepadManager->gamepadName(gamepad).isEmpty()) { + gamepadName = QString("Gamepad %1").arg(gamepad); + } else { + gamepadName = gamepadManager->gamepadName(gamepad); + } + if (name == gamepadName) + { + inputController = new GamepadInputController(gamepad); + if (inputController) + { + qDebug() << "GamepadInputController::open: Opened gamepad " << name; + } + else + { + qDebug() << "GamepadInputController::open: Failed to open gamepad: " << gamepad; + } + } + } + } + + return inputController; +} diff --git a/plugins/feature/gs232controller/gamepadinputcontroller.h b/plugins/feature/gs232controller/gamepadinputcontroller.h new file mode 100644 index 000000000..d881b03d2 --- /dev/null +++ b/plugins/feature/gs232controller/gamepadinputcontroller.h @@ -0,0 +1,53 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FEATURE_GAMEPADINPUTCONTROLLER_H_ +#define INCLUDE_FEATURE_GAMEPADINPUTCONTROLLER_H_ + +#include "inputcontroller.h" + +#include + +class GamepadInputController : public InputController { + +public: + + GamepadInputController(int deviceId); + double getAxisValue(int axis) override; + int getNumberOfAxes() const override; + + static QStringList getAllControllers(); + static GamepadInputController* open(const QString& name); + +private slots: + + void axisRightXChanged(double value); + void axisRightYChanged(double value); + void axisLeftXChanged(double value); + void axisLeftYChanged(double value); + +private: + + QGamepad m_gamepad; + double m_rightX; + double m_rightY; + double m_leftX; + double m_leftY; +}; + +#endif // INCLUDE_FEATURE_GAMEPADINPUTCONTROLLER_H_ + diff --git a/plugins/feature/gs232controller/gs232controller.cpp b/plugins/feature/gs232controller/gs232controller.cpp index 6f58478be..ba8fda383 100644 --- a/plugins/feature/gs232controller/gs232controller.cpp +++ b/plugins/feature/gs232controller/gs232controller.cpp @@ -491,6 +491,8 @@ void GS232Controller::webapiFormatFeatureSettings( response.getGs232ControllerSettings()->setProtocol(settings.m_protocol); response.getGs232ControllerSettings()->setPrecision(settings.m_precision); response.getGs232ControllerSettings()->setCoordinates((int)settings.m_coordinates); + response.getGs232ControllerSettings()->setInputController(new QString(settings.m_inputController)); + response.getGs232ControllerSettings()->setInputSensitivity(settings.m_inputSensitivity); if (response.getGs232ControllerSettings()->getTitle()) { *response.getGs232ControllerSettings()->getTitle() = settings.m_title; @@ -585,6 +587,12 @@ void GS232Controller::webapiUpdateFeatureSettings( if (featureSettingsKeys.contains("coordinates")) { settings.m_coordinates = (GS232ControllerSettings::Coordinates)response.getGs232ControllerSettings()->getCoordinates(); } + if (featureSettingsKeys.contains("inputController")) { + settings.m_inputController = *response.getGs232ControllerSettings()->getInputController(); + } + if (featureSettingsKeys.contains("inputSensitivity")) { + settings.m_inputSensitivity = response.getGs232ControllerSettings()->getInputSensitivity(); + } if (featureSettingsKeys.contains("title")) { settings.m_title = *response.getGs232ControllerSettings()->getTitle(); } @@ -676,6 +684,12 @@ void GS232Controller::webapiReverseSendSettings(const QList& featureSet if (featureSettingsKeys.contains("coordinates") || force) { swgGS232ControllerSettings->setCoordinates(settings.m_coordinates); } + if (featureSettingsKeys.contains("inputController") || force) { + swgGS232ControllerSettings->setInputController(new QString(settings.m_inputController)); + } + if (featureSettingsKeys.contains("inputSensitivity") || force) { + swgGS232ControllerSettings->setInputSensitivity(settings.m_inputSensitivity); + } if (featureSettingsKeys.contains("title") || force) { swgGS232ControllerSettings->setTitle(new QString(settings.m_title)); } diff --git a/plugins/feature/gs232controller/gs232controllergui.cpp b/plugins/feature/gs232controller/gs232controllergui.cpp index 10450a9ea..4dd44c30a 100644 --- a/plugins/feature/gs232controller/gs232controllergui.cpp +++ b/plugins/feature/gs232controller/gs232controllergui.cpp @@ -216,7 +216,13 @@ GS232ControllerGUI::GS232ControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featu m_doApplySettings(true), m_lastFeatureState(0), m_lastOnTarget(false), - m_dfmStatusDialog() + m_dfmStatusDialog(), + m_inputController(nullptr), + m_inputCoord1(0.0), + m_inputCoord2(0.0), + m_inputAzOffset(0.0), + m_inputElOffset(0.0), + m_inputUpdate(false) { m_feature = feature; setAttribute(Qt::WA_DeleteOnClose, true); @@ -246,6 +252,10 @@ GS232ControllerGUI::GS232ControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featu m_settings.setRollupState(&m_rollupState); + updateInputControllerList(); + connect(InputControllerManager::instance(), &InputControllerManager::controllersChanged, this, &GS232ControllerGUI::updateInputControllerList); + connect(&m_inputTimer, &QTimer::timeout, this, &GS232ControllerGUI::checkInputController); + displaySettings(); applySettings(true); makeUIConnections(); @@ -256,6 +266,123 @@ GS232ControllerGUI::GS232ControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featu new DialogPositioner(&m_dfmStatusDialog, true); } +void GS232ControllerGUI::updateInputControllerList() +{ + ui->inputController->blockSignals(true); + ui->inputController->clear(); + ui->inputController->addItem("None"); + + QStringList controllers = InputControllerManager::getAllControllers(); + for (const auto& controller : controllers) { + ui->inputController->addItem(controller); + } + ui->inputController->blockSignals(false); + int index = ui->inputController->findText(m_settings.m_inputController); + ui->inputController->setCurrentIndex(index); +} + +void GS232ControllerGUI::updateInputController() +{ + delete m_inputController; + m_inputController = nullptr; + + bool enabled = false; + if (m_settings.m_inputController != "None") + { + m_inputController = InputControllerManager::open(m_settings.m_inputController); + if (m_inputController) + { + m_inputTimer.start(20); + enabled = true; + } + } + else + { + m_inputTimer.stop(); + } + + ui->inputSensitivityLabel->setEnabled(enabled); + ui->inputSensitivity->setEnabled(enabled); + ui->inputSensitivityText->setEnabled(enabled); +} + +void GS232ControllerGUI::checkInputController() +{ + if (m_inputController) + { + // If our input device has two sticks (four axes), we use one for target and one for offset + // If only one stick (two axes), it's used both for target when not tracking and offset, when tracking + // Use separate variables rather than values in UI, to allow for higher precision + + if (!m_settings.m_track) + { + m_inputCoord1 += m_settings.m_inputSensitivity * m_inputController->getAxisValue(0); + m_inputCoord2 += m_settings.m_inputSensitivity * -m_inputController->getAxisValue(1); + + if (m_settings.m_coordinates == GS232ControllerSettings::AZ_EL) + { + m_inputCoord1 = std::max(m_inputCoord1, (double) m_settings.m_azimuthMin); + m_inputCoord1 = std::min(m_inputCoord1, (double) m_settings.m_azimuthMax); + m_inputCoord2 = std::max(m_inputCoord2, (double) m_settings.m_elevationMin); + m_inputCoord2 = std::min(m_inputCoord2, (double) m_settings.m_elevationMax); + } + else + { + m_inputCoord1 = std::max(m_inputCoord1, -90.0); + m_inputCoord1 = std::min(m_inputCoord1, 90.0); + m_inputCoord2 = std::max(m_inputCoord2, -90.0); + m_inputCoord2 = std::min(m_inputCoord2, 90.0); + } + } + + if ((m_inputController->getNumberOfAxes() < 4) && m_settings.m_track) + { + m_inputAzOffset += m_settings.m_inputSensitivity * m_inputController->getAxisValue(0); + m_inputElOffset += m_settings.m_inputSensitivity * -m_inputController->getAxisValue(1); + } + else if (m_inputController->getNumberOfAxes() >= 4) + { + m_inputAzOffset += m_settings.m_inputSensitivity * m_inputController->getAxisValue(2); + m_inputElOffset += m_settings.m_inputSensitivity * -m_inputController->getAxisValue(3); + } + m_inputAzOffset = std::max(m_inputAzOffset, -360.0); + m_inputAzOffset = std::min(m_inputAzOffset, 360.0); + m_inputElOffset = std::max(m_inputElOffset, -180.0); + m_inputElOffset = std::min(m_inputElOffset, 180.0); + + m_inputUpdate = true; + if (!m_settings.m_track) + { + ui->coord1->setValue(m_inputCoord1); + ui->coord2->setValue(m_inputCoord2); + } + if (((m_inputController->getNumberOfAxes() < 4) && m_settings.m_track) || (m_inputController->getNumberOfAxes() >= 4)) + { + ui->azimuthOffset->setValue(m_inputAzOffset); + ui->elevationOffset->setValue(m_inputElOffset); + } + m_inputUpdate = false; + } +} + +void GS232ControllerGUI::on_inputController_currentIndexChanged(int index) +{ + // Don't update settings if set to -1 + if (index >= 0) + { + m_settings.m_inputController = ui->inputController->currentText(); + applySettings(); + updateInputController(); + } +} + +void GS232ControllerGUI::on_inputSensitivty_valueChanged(int value) +{ + m_settings.m_inputSensitivity = value / 1000.0; + ui->inputSensitivityText->setText(QString("%1%").arg(m_settings.m_inputSensitivity * 100.0)); + applySettings(); +} + GS232ControllerGUI::~GS232ControllerGUI() { m_dfmStatusDialog.close(); @@ -302,6 +429,9 @@ void GS232ControllerGUI::displaySettings() ui->elevationMin->setValue(m_settings.m_elevationMin); ui->elevationMax->setValue(m_settings.m_elevationMax); ui->tolerance->setValue(m_settings.m_tolerance); + ui->inputController->setCurrentText(m_settings.m_inputController); + ui->inputSensitivity->setValue((int) (m_settings.m_inputSensitivity * 1000.0)); + ui->inputSensitivityText->setText(QString("%1%").arg(m_settings.m_inputSensitivity * 100.0)); ui->dfmTrack->setChecked(m_settings.m_dfmTrackOn); ui->dfmLubePumps->setChecked(m_settings.m_dfmLubePumpsOn); ui->dfmBrakes->setChecked(m_settings.m_dfmBrakesOn); @@ -543,18 +673,27 @@ void GS232ControllerGUI::on_port_valueChanged(int value) void GS232ControllerGUI::on_coord1_valueChanged(double value) { + if (!m_inputUpdate) { + m_inputCoord1 = value; + } displayToAzEl(value, ui->coord2->value()); ui->targetName->setText(""); } void GS232ControllerGUI::on_coord2_valueChanged(double value) { + if (!m_inputUpdate) { + m_inputCoord2 = value; + } displayToAzEl(ui->coord1->value(), value); ui->targetName->setText(""); } void GS232ControllerGUI::on_azimuthOffset_valueChanged(int value) { + if (!m_inputUpdate) { + m_inputAzOffset = value; + } m_settings.m_azimuthOffset = value; m_settingsKeys.append("azimuthOffset"); applySettings(); @@ -562,6 +701,9 @@ void GS232ControllerGUI::on_azimuthOffset_valueChanged(int value) void GS232ControllerGUI::on_elevationOffset_valueChanged(int value) { + if (!m_inputUpdate) { + m_inputElOffset = value; + } m_settings.m_elevationOffset = value; m_settingsKeys.append("elevationOffset"); applySettings(); @@ -805,6 +947,8 @@ void GS232ControllerGUI::makeUIConnections() QObject::connect(ui->tolerance, qOverload(&QDoubleSpinBox::valueChanged), this, &GS232ControllerGUI::on_tolerance_valueChanged); QObject::connect(ui->precision, qOverload(&QSpinBox::valueChanged), this, &GS232ControllerGUI::on_precision_valueChanged); QObject::connect(ui->coordinates, qOverload(&QComboBox::currentIndexChanged), this, &GS232ControllerGUI::on_coordinates_currentIndexChanged); + QObject::connect(ui->inputController, qOverload(&QComboBox::currentIndexChanged), this, &GS232ControllerGUI::on_inputController_currentIndexChanged); + QObject::connect(ui->inputSensitivity, qOverload(&QSlider::valueChanged), this, &GS232ControllerGUI::on_inputSensitivty_valueChanged); QObject::connect(ui->dfmTrack, &QToolButton::toggled, this, &GS232ControllerGUI::on_dfmTrack_clicked); QObject::connect(ui->dfmLubePumps, &QToolButton::toggled, this, &GS232ControllerGUI::on_dfmLubePumps_clicked); QObject::connect(ui->dfmBrakes, &QToolButton::toggled, this, &GS232ControllerGUI::on_dfmBrakes_clicked); diff --git a/plugins/feature/gs232controller/gs232controllergui.h b/plugins/feature/gs232controller/gs232controllergui.h index 2005d363b..55bf01612 100644 --- a/plugins/feature/gs232controller/gs232controllergui.h +++ b/plugins/feature/gs232controller/gs232controllergui.h @@ -27,6 +27,7 @@ #include "gs232controllersettings.h" #include "dfmstatusdialog.h" +#include "inputcontroller.h" class PluginAPI; class FeatureUISet; @@ -68,6 +69,14 @@ private: DFMStatusDialog m_dfmStatusDialog; + InputController *m_inputController; + QTimer m_inputTimer; + double m_inputCoord1; + double m_inputCoord2; + double m_inputAzOffset; + double m_inputElOffset; + bool m_inputUpdate; + explicit GS232ControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr); virtual ~GS232ControllerGUI(); @@ -84,6 +93,7 @@ private: void makeUIConnections(); void azElToDisplay(float az, float el, float& coord1, float& coord2) const; void displayToAzEl(float coord1, float coord2); + void updateInputController(); private slots: void onMenuDialogCalled(const QPoint &p); @@ -115,6 +125,10 @@ private slots: void on_dfmDrives_clicked(bool checked=false); void on_dfmShowStatus_clicked(); void updateStatus(); + void on_inputController_currentIndexChanged(int index); + void on_inputSensitivty_valueChanged(int value); + void updateInputControllerList(); + void checkInputController(); }; #endif // INCLUDE_FEATURE_GS232CONTROLLERGUI_H_ diff --git a/plugins/feature/gs232controller/gs232controllergui.ui b/plugins/feature/gs232controller/gs232controllergui.ui index 4a26a83c6..6edfed6f0 100644 --- a/plugins/feature/gs232controller/gs232controllergui.ui +++ b/plugins/feature/gs232controller/gs232controllergui.ui @@ -306,7 +306,7 @@ 10 140 341 - 191 + 201 @@ -330,6 +330,84 @@ + + + + Baud rate + + + + + + + Gamepad / joystick to use + + + + None + + + + + + + + 450 + + + + + + + Elevation offset + + + + + + + Azimuth max + + + + + + + Coordinates + + + + + + + Azimuth min + + + + + + + 180 + + + + + + + false + + + Sensitivity + + + + + + + Elevation max + + + @@ -337,6 +415,56 @@ + + + + Specify an offset angle in degrees that will be added to the target elevation to correct for misalignment + + + -180 + + + 180 + + + 1 + + + + + + + Command protocol + + + + GS-232 + + + + + SPID + + + + + rotctld + + + + + DFM + + + + + + + + Port + + + @@ -353,64 +481,6 @@ - - - - Hostname / IP address of computer to connect to - - - - - - - 450 - - - - - - - Tolerance - - - - - - - Name of serial port to use to connect to the rotator - - - true - - - - - - - Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment - - - -360 - - - 360 - - - - - - - Azimuth max - - - - - - - Connection - - - @@ -421,110 +491,13 @@ - - - - Baud rate - - - - - - - The type of connection to use to the rotator - - - - Serial - - - - - TCP - - - - - - - - Serial Port - - - - - - - Tolerance in degrees - - - 0 - - - - - + + 450 - - - - Host - - - - - - - Port - - - - - - - Elevation max - - - - - - - Elevation min - - - - - - - 180 - - - - - - - Azimuth min - - - - - - - Elevation offset - - - - - - - 180 - - - @@ -585,6 +558,61 @@ + + + + Connection + + + + + + + The type of connection to use to the rotator + + + + Serial + + + + + TCP + + + + + + + + Name of serial port to use to connect to the rotator + + + true + + + + + + + Host + + + + + + + 180 + + + + + + + Hostname / IP address of computer to connect to + + + @@ -595,49 +623,6 @@ - - - - Command protocol - - - - GS-232 - - - - - SPID - - - - - rotctld - - - - - DFM - - - - - - - - Specify an offset angle in degrees that will be added to the target elevation to correct for misalignment - - - -180 - - - 180 - - - 1 - - - @@ -645,6 +630,50 @@ + + + + Elevation min + + + + + + + Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment + + + -360 + + + 360 + + + + + + + Tolerance in degrees + + + 0 + + + + + + + Serial Port + + + + + + + Tolerance + + + @@ -667,13 +696,52 @@ - - + + - Coordinates + Input Control + + + + + + false + + + Input controller sensitivity + + + 1 + + + 2000 + + + 10 + + + 25 + + + Qt::Horizontal + + + + + + + false + + + 100% + + + + + diff --git a/plugins/feature/gs232controller/gs232controllersettings.cpp b/plugins/feature/gs232controller/gs232controllersettings.cpp index 8311a957b..2a822286d 100644 --- a/plugins/feature/gs232controller/gs232controllersettings.cpp +++ b/plugins/feature/gs232controller/gs232controllersettings.cpp @@ -64,6 +64,8 @@ void GS232ControllerSettings::resetToDefaults() m_connection = SERIAL; m_precision = 0; m_coordinates = AZ_EL; + m_inputController = "None"; + m_inputSensitivity = 0.25; m_dfmTrackOn = false; m_dfmLubePumpsOn = false; m_dfmBrakesOn = false; @@ -119,6 +121,8 @@ QByteArray GS232ControllerSettings::serialize() const s.writeBool(32, m_dfmLubePumpsOn); s.writeBool(33, m_dfmBrakesOn); s.writeBool(34, m_dfmDrivesOn); + s.writeString(35, m_inputController); + s.writeFloat(36, m_inputSensitivity); return s.final(); } @@ -187,6 +191,8 @@ bool GS232ControllerSettings::deserialize(const QByteArray& data) d.readBool(32, &m_dfmLubePumpsOn); d.readBool(33, &m_dfmBrakesOn); d.readBool(34, &m_dfmDrivesOn); + d.readString(35, &m_inputController, "None"); + d.readFloat(36, &m_inputSensitivity, 0.25); return true; } @@ -271,6 +277,12 @@ void GS232ControllerSettings::applySettings(const QStringList& settingsKeys, con if (settingsKeys.contains("coordinates")) { m_coordinates = settings.m_coordinates; } + if (settingsKeys.contains("inputController")) { + m_inputController = settings.m_inputController; + } + if (settingsKeys.contains("inputSensitivity")) { + m_inputSensitivity = settings.m_inputSensitivity; + } if (settingsKeys.contains("dfmTrackOn")) { m_dfmTrackOn = settings.m_dfmTrackOn; } @@ -370,6 +382,12 @@ QString GS232ControllerSettings::getDebugString(const QStringList& settingsKeys, if (settingsKeys.contains("coordinates") || force) { ostr << " m_coordinates: " << m_precision; } + if (settingsKeys.contains("inputController") || force) { + ostr << " m_inputController: " << m_inputController.toStdString(); + } + if (settingsKeys.contains("inputSensitivity") || force) { + ostr << " m_inputSensitivity: " << m_inputSensitivity; + } if (settingsKeys.contains("title") || force) { ostr << " m_title: " << m_title.toStdString(); } @@ -397,4 +415,3 @@ QString GS232ControllerSettings::getDebugString(const QStringList& settingsKeys, return QString(ostr.str().c_str()); } - diff --git a/plugins/feature/gs232controller/gs232controllersettings.h b/plugins/feature/gs232controller/gs232controllersettings.h index eff7863f1..1d116b05d 100644 --- a/plugins/feature/gs232controller/gs232controllersettings.h +++ b/plugins/feature/gs232controller/gs232controllersettings.h @@ -62,6 +62,8 @@ struct GS232ControllerSettings enum Connection { SERIAL, TCP } m_connection; int m_precision; enum Coordinates { AZ_EL, X_Y_85, X_Y_30 } m_coordinates; + QString m_inputController; + float m_inputSensitivity; bool m_dfmTrackOn; bool m_dfmLubePumpsOn; diff --git a/plugins/feature/gs232controller/inputcontroller.cpp b/plugins/feature/gs232controller/inputcontroller.cpp new file mode 100644 index 000000000..e7f8f8df4 --- /dev/null +++ b/plugins/feature/gs232controller/inputcontroller.cpp @@ -0,0 +1,63 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifdef QT_GAMEPAD_FOUND +#include +#include "gamepadinputcontroller.h" +#endif + +#include "inputcontroller.h" + +InputControllerManager* InputControllerManager::m_instance = nullptr; + +QStringList InputControllerManager::getAllControllers() +{ +#ifdef QT_GAMEPAD_FOUND + return GamepadInputController::getAllControllers(); +#else + return {}; +#endif +} + +InputController* InputControllerManager::open(const QString& name) +{ +#ifdef QT_GAMEPAD_FOUND + return GamepadInputController::open(name); +#else + return nullptr; +#endif +} + +InputControllerManager* InputControllerManager::instance() +{ + if (!m_instance) { + m_instance = new InputControllerManager(); + } + return m_instance; +} + +InputControllerManager::InputControllerManager() +{ +#ifdef QT_GAMEPAD_FOUND + connect(QGamepadManager::instance(), &QGamepadManager::connectedGamepadsChanged, this, &InputControllerManager::connectedGamepadsChanged); +#endif +} + +void InputControllerManager::connectedGamepadsChanged() +{ + emit controllersChanged(); +} diff --git a/plugins/feature/gs232controller/inputcontroller.h b/plugins/feature/gs232controller/inputcontroller.h new file mode 100644 index 000000000..d35306c4b --- /dev/null +++ b/plugins/feature/gs232controller/inputcontroller.h @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FEATURE_INPUTCONTROLLER_H_ +#define INCLUDE_FEATURE_INPUTCONTROLLER_H_ + +#include + +class InputController : public QObject { + Q_OBJECT +public: + + // Called every ~50ms + // axis 0-3. 0=Az/X, 1=El/Y, 2=Az Offset, 3=El Offset + // value returned should be current axis position in range [-1,1] + virtual double getAxisValue(int axis) = 0; + virtual int getNumberOfAxes() const = 0; + +}; + +class InputControllerManager : public QObject { + Q_OBJECT +public: + + static QStringList getAllControllers(); + static InputController* open(const QString& name); + static InputControllerManager* instance(); + +signals: + + void controllersChanged(); + +private slots: + void connectedGamepadsChanged(); + +private: + InputControllerManager(); + + static InputControllerManager *m_instance; + +}; + + +#endif // INCLUDE_FEATURE_INPUTCONTROLLER_H_ diff --git a/plugins/feature/gs232controller/readme.md b/plugins/feature/gs232controller/readme.md index 34cd7794f..34770c183 100644 --- a/plugins/feature/gs232controller/readme.md +++ b/plugins/feature/gs232controller/readme.md @@ -2,10 +2,10 @@

Introduction

-The Rotator Controller feature plugin allows SDRangel to send commands to GS-232 and SPID rotators as well as hamlib's rotctld, via a serial or TCP connection. +The Rotator Controller feature plugin allows SDRangel to send commands to GS-232 and SPID rotators as well as hamlib's rotctld, via a serial or TCP connection. This allows SDRangel to point antennas mounted on a rotator to a specified azimuth and elevation. -Azimuth and elevation can be set manually by a user in the GUI, via the REST API, or via another plugin, such as the Map Feature, the ADS-B Demodulator, or the Star Tracker. +Azimuth and elevation can be set manually by a user in the GUI, via the REST API, via another plugin, such as the Map Feature, the ADS-B Demodulator, or the Star Tracker, or by controller/gamepads (such as an XBox Controller).

Interface

@@ -107,6 +107,20 @@ Specifies the coordinate system used by the GUI for entry and display of the pos Equations for translating between these coordinate systems can be found [here](https://ntrs.nasa.gov/citations/19670030005). +

22: Input Control

+ +Specifies a controller/gamepad (such as an XBox Wireless Controller) that can be used to specify target coordinates or azimuth and elevation offset. + +When a controller with 2 sticks (4 axes) such as the XBox Wireless Controller is used, the right stick is used for controlling target coordinates, +while the left stick is for controlling azimuth and elevation offset. +If a controller only has 2 axes, target coordinates will be controlled when not tracking (6) and offset will be controlled when tracking. + +The [Qt Gamepad](https://doc.qt.io/qt-5/qtgamepad-index.html) library is used to implement gamepad support. + +

23: Sensitivity

+ +Specifies the sensitivity on the input controls (22). The higher the value, the faster coordinates will change for a given control stick movement. +

Protocol Implementations

GS-232 Protocol Implementation Notes

diff --git a/swagger/sdrangel/api/swagger/include/GS232Controller.yaml b/swagger/sdrangel/api/swagger/include/GS232Controller.yaml index b555a959e..8e47d320e 100644 --- a/swagger/sdrangel/api/swagger/include/GS232Controller.yaml +++ b/swagger/sdrangel/api/swagger/include/GS232Controller.yaml @@ -58,6 +58,13 @@ GS232ControllerSettings: coordinates: description: (0 Az/El, 1 X/Y 85, 2 X/Y 30) type: integer + inputController: + description: Name of input controller + type: string + inputSensitivity: + description: Input controller sensitivity + type: number + format: float title: type: string rgbColor: diff --git a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp index 954e40380..309e2ee1e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp @@ -64,6 +64,10 @@ SWGGS232ControllerSettings::SWGGS232ControllerSettings() { m_precision_isSet = false; coordinates = 0; m_coordinates_isSet = false; + input_controller = nullptr; + m_input_controller_isSet = false; + input_sensitivity = 0.0f; + m_input_sensitivity_isSet = false; title = nullptr; m_title_isSet = false; rgb_color = 0; @@ -124,6 +128,10 @@ SWGGS232ControllerSettings::init() { m_precision_isSet = false; coordinates = 0; m_coordinates_isSet = false; + input_controller = new QString(""); + m_input_controller_isSet = false; + input_sensitivity = 0.0f; + m_input_sensitivity_isSet = false; title = new QString(""); m_title_isSet = false; rgb_color = 0; @@ -168,6 +176,10 @@ SWGGS232ControllerSettings::cleanup() { + if(input_controller != nullptr) { + delete input_controller; + } + if(title != nullptr) { delete title; } @@ -231,6 +243,10 @@ SWGGS232ControllerSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&coordinates, pJson["coordinates"], "qint32", ""); + ::SWGSDRangel::setValue(&input_controller, pJson["inputController"], "QString", "QString"); + + ::SWGSDRangel::setValue(&input_sensitivity, pJson["inputSensitivity"], "float", ""); + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); @@ -317,6 +333,12 @@ SWGGS232ControllerSettings::asJsonObject() { if(m_coordinates_isSet){ obj->insert("coordinates", QJsonValue(coordinates)); } + if(input_controller != nullptr && *input_controller != QString("")){ + toJsonValue(QString("inputController"), input_controller, obj, QString("QString")); + } + if(m_input_sensitivity_isSet){ + obj->insert("inputSensitivity", QJsonValue(input_sensitivity)); + } if(title != nullptr && *title != QString("")){ toJsonValue(QString("title"), title, obj, QString("QString")); } @@ -525,6 +547,26 @@ SWGGS232ControllerSettings::setCoordinates(qint32 coordinates) { this->m_coordinates_isSet = true; } +QString* +SWGGS232ControllerSettings::getInputController() { + return input_controller; +} +void +SWGGS232ControllerSettings::setInputController(QString* input_controller) { + this->input_controller = input_controller; + this->m_input_controller_isSet = true; +} + +float +SWGGS232ControllerSettings::getInputSensitivity() { + return input_sensitivity; +} +void +SWGGS232ControllerSettings::setInputSensitivity(float input_sensitivity) { + this->input_sensitivity = input_sensitivity; + this->m_input_sensitivity_isSet = true; +} + QString* SWGGS232ControllerSettings::getTitle() { return title; @@ -664,6 +706,12 @@ SWGGS232ControllerSettings::isSet(){ if(m_coordinates_isSet){ isObjectUpdated = true; break; } + if(input_controller && *input_controller != QString("")){ + isObjectUpdated = true; break; + } + if(m_input_sensitivity_isSet){ + isObjectUpdated = true; break; + } if(title && *title != QString("")){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h index eff95dede..3cc129009 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h @@ -97,6 +97,12 @@ public: qint32 getCoordinates(); void setCoordinates(qint32 coordinates); + QString* getInputController(); + void setInputController(QString* input_controller); + + float getInputSensitivity(); + void setInputSensitivity(float input_sensitivity); + QString* getTitle(); void setTitle(QString* title); @@ -179,6 +185,12 @@ private: qint32 coordinates; bool m_coordinates_isSet; + QString* input_controller; + bool m_input_controller_isSet; + + float input_sensitivity; + bool m_input_sensitivity_isSet; + QString* title; bool m_title_isSet; From 62e0ae41f334301f2721204b8330e5517c3a747c Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Sun, 23 Apr 2023 19:56:41 +0100 Subject: [PATCH 2/7] Only needed for GUI --- CMakeLists.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61b3c54ad..78980d373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -610,9 +610,7 @@ else() MultimediaWidgets Positioning Charts - SerialPort - OPTIONAL_COMPONENTS - Gamepad) + SerialPort) endif() # for the server we don't need OpenGL/Qt Quick components @@ -664,7 +662,8 @@ if (BUILD_GUI) OPTIONAL_COMPONENTS WebEngine WebEngineCore - WebEngineWidgets) + WebEngineWidgets + Gamepad) endif() endif() endif() From fe3aeaed5b45820fbdb41ce4e9dc40751cf289fc Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Mon, 24 Apr 2023 11:38:52 +0100 Subject: [PATCH 3/7] Add support for gamepad axis configuration --- .../feature/gs232controller/CMakeLists.txt | 11 +- .../gamepadconfigurationdialog.cpp | 139 +++++++++++++ .../gamepadconfigurationdialog.h | 49 +++++ .../gamepadconfigurationdialog.ui | 196 ++++++++++++++++++ .../gamepadinputcontroller.cpp | 23 ++ .../gs232controller/gamepadinputcontroller.h | 2 + .../gs232controller/gs232controllergui.cpp | 10 + .../gs232controller/gs232controllergui.h | 1 + .../gs232controller/gs232controllergui.ui | 38 ++-- .../feature/gs232controller/inputcontroller.h | 2 + plugins/feature/gs232controller/readme.md | 9 +- 11 files changed, 463 insertions(+), 17 deletions(-) create mode 100644 plugins/feature/gs232controller/gamepadconfigurationdialog.cpp create mode 100644 plugins/feature/gs232controller/gamepadconfigurationdialog.h create mode 100644 plugins/feature/gs232controller/gamepadconfigurationdialog.ui diff --git a/plugins/feature/gs232controller/CMakeLists.txt b/plugins/feature/gs232controller/CMakeLists.txt index 2d3a24fd4..9961e3e43 100644 --- a/plugins/feature/gs232controller/CMakeLists.txt +++ b/plugins/feature/gs232controller/CMakeLists.txt @@ -55,8 +55,15 @@ if(NOT SERVER_MODE) if(Qt${QT_DEFAULT_MAJOR_VERSION}Gamepad_FOUND) add_compile_definitions(QT_GAMEPAD_FOUND) set(TARGET_LIB ${TARGET_LIB} Qt::Gamepad) - set(gs232controller_SOURCES ${gs232controller_SOURCES} gamepadinputcontroller.cpp) - set(gs232controller_HEADERS ${gs232controller_HEADERS} gamepadinputcontroller.h) + set(gs232controller_SOURCES + ${gs232controller_SOURCES} + gamepadinputcontroller.cpp + gamepadconfigurationdialog.cpp + gamepadconfigurationdialog.ui) + set(gs232controller_HEADERS + ${gs232controller_HEADERS} + gamepadinputcontroller.h + gamepadconfiguration.h) endif() else() diff --git a/plugins/feature/gs232controller/gamepadconfigurationdialog.cpp b/plugins/feature/gs232controller/gamepadconfigurationdialog.cpp new file mode 100644 index 000000000..79abdcb4b --- /dev/null +++ b/plugins/feature/gs232controller/gamepadconfigurationdialog.cpp @@ -0,0 +1,139 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "gamepadconfigurationdialog.h" + +GamepadConfigurationDialog::GamepadConfigurationDialog(QGamepad *gamepad, QWidget* parent) : + QDialog(parent), + ui(new Ui::GamepadConfigurationDialog), + m_gamepad(gamepad) +{ + ui->setupUi(this); + connect(m_gamepad, &QGamepad::axisRightXChanged, this, &GamepadConfigurationDialog::axisRightXChanged); + connect(m_gamepad, &QGamepad::axisRightYChanged, this, &GamepadConfigurationDialog::axisRightYChanged); + connect(m_gamepad, &QGamepad::axisLeftXChanged, this, &GamepadConfigurationDialog::axisLeftXChanged); + connect(m_gamepad, &QGamepad::axisLeftYChanged, this, &GamepadConfigurationDialog::axisLeftYChanged); +} + +GamepadConfigurationDialog::~GamepadConfigurationDialog() +{ + delete ui; +} + +void GamepadConfigurationDialog::accept() +{ + QDialog::accept(); +} + +void GamepadConfigurationDialog::on_config0_clicked() +{ + if (ui->config0->text() == "Configure") + { + ui->config0->setText("Done"); + ui->config1->setEnabled(false); + ui->config2->setEnabled(false); + ui->config3->setEnabled(false); + QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisRightX); + } + else + { + ui->config0->setText("Configure"); + ui->config1->setEnabled(true); + ui->config2->setEnabled(true); + ui->config3->setEnabled(true); + } +} + +void GamepadConfigurationDialog::on_config1_clicked() +{ + if (ui->config1->text() == "Configure") + { + ui->config1->setText("Done"); + ui->config0->setEnabled(false); + ui->config2->setEnabled(false); + ui->config3->setEnabled(false); + QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisRightY); + } + else + { + ui->config1->setText("Configure"); + ui->config0->setEnabled(true); + ui->config2->setEnabled(true); + ui->config3->setEnabled(true); + } +} + +void GamepadConfigurationDialog::on_config2_clicked() +{ + if (ui->config2->text() == "Configure") + { + ui->config2->setText("Done"); + ui->config0->setEnabled(false); + ui->config1->setEnabled(false); + ui->config3->setEnabled(false); + QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisLeftX); + } + else + { + ui->config2->setText("Configure"); + ui->config0->setEnabled(true); + ui->config1->setEnabled(true); + ui->config3->setEnabled(true); + } +} + +void GamepadConfigurationDialog::on_config3_clicked() +{ + if (ui->config3->text() == "Configure") + { + ui->config3->setText("Done"); + ui->config0->setEnabled(false); + ui->config1->setEnabled(false); + ui->config2->setEnabled(false); + QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisLeftY); + } + else + { + ui->config3->setText("Configure"); + ui->config0->setEnabled(true); + ui->config1->setEnabled(true); + ui->config2->setEnabled(true); + } +} + +void GamepadConfigurationDialog::axisRightXChanged(double value) +{ + ui->value0->setText(QString::number(value)); +} + +void GamepadConfigurationDialog::axisRightYChanged(double value) +{ + ui->value1->setText(QString::number(value)); +} + +void GamepadConfigurationDialog::axisLeftXChanged(double value) +{ + ui->value2->setText(QString::number(value)); +} + +void GamepadConfigurationDialog::axisLeftYChanged(double value) +{ + ui->value3->setText(QString::number(value)); +} diff --git a/plugins/feature/gs232controller/gamepadconfigurationdialog.h b/plugins/feature/gs232controller/gamepadconfigurationdialog.h new file mode 100644 index 000000000..4f0db8551 --- /dev/null +++ b/plugins/feature/gs232controller/gamepadconfigurationdialog.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_GAMEPADCONFIGURATIONDIALOG_H +#define INCLUDE_GAMEPADCONFIGURATIONDIALOG_H + +#include "ui_gamepadconfigurationdialog.h" + +class QGamepad; + +class GamepadConfigurationDialog : public QDialog { + Q_OBJECT + +public: + explicit GamepadConfigurationDialog(QGamepad *gamepad, QWidget* parent = 0); + ~GamepadConfigurationDialog(); + +private slots: + void accept(); + void on_config0_clicked(); + void on_config1_clicked(); + void on_config2_clicked(); + void on_config3_clicked(); + void axisRightXChanged(double value); + void axisRightYChanged(double value); + void axisLeftXChanged(double value); + void axisLeftYChanged(double value); + +private: + Ui::GamepadConfigurationDialog* ui; + QGamepad *m_gamepad; +}; + +#endif // INCLUDE_GAMEPADCONFIGURATIONDIALOG_H + diff --git a/plugins/feature/gs232controller/gamepadconfigurationdialog.ui b/plugins/feature/gs232controller/gamepadconfigurationdialog.ui new file mode 100644 index 000000000..34a83f83c --- /dev/null +++ b/plugins/feature/gs232controller/gamepadconfigurationdialog.ui @@ -0,0 +1,196 @@ + + + GamepadConfigurationDialog + + + + 0 + 0 + 380 + 309 + + + + + Liberation Sans + 9 + + + + Gamepad Configuration + + + + + + + + + 0 + + + + + + + Configure + + + + + + + 0 + + + + + + + Configure + + + + + + + Azimuth Offset Axis + + + + + + + 0 + + + + + + + Configure + + + + + + + Elevation Offset Axis + + + + + + + Target Elevation/Y Axis + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Target Aziumth/X Axis + + + + + + + 0 + + + + + + + + 0 + 80 + + + + To configure a controller axis: + + - press the Configure button, + - move the controller axis, + - then press Done + + + + + + + Configure + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + config0 + config1 + config2 + config3 + + + + + buttonBox + accepted() + GamepadConfigurationDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + GamepadConfigurationDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/feature/gs232controller/gamepadinputcontroller.cpp b/plugins/feature/gs232controller/gamepadinputcontroller.cpp index 7dc8c78d9..3585446e2 100644 --- a/plugins/feature/gs232controller/gamepadinputcontroller.cpp +++ b/plugins/feature/gs232controller/gamepadinputcontroller.cpp @@ -19,6 +19,7 @@ #include #include "gamepadinputcontroller.h" +#include "gamepadconfigurationdialog.h" GamepadInputController::GamepadInputController(int deviceId) : m_gamepad(deviceId), @@ -54,6 +55,28 @@ int GamepadInputController::getNumberOfAxes() const return 4; } +bool GamepadInputController::supportsConfiguration() const +{ + // Should only return true on Linux evdev or Android + return QGamepadManager::instance()->isConfigurationNeeded(m_gamepad.deviceId()); +} + +void GamepadInputController::configure() +{ + disconnect(&m_gamepad, &QGamepad::axisRightXChanged, this, &GamepadInputController::axisRightXChanged); + disconnect(&m_gamepad, &QGamepad::axisRightYChanged, this, &GamepadInputController::axisRightYChanged); + disconnect(&m_gamepad, &QGamepad::axisLeftXChanged, this, &GamepadInputController::axisLeftXChanged); + disconnect(&m_gamepad, &QGamepad::axisLeftYChanged, this, &GamepadInputController::axisLeftYChanged); + + GamepadConfigurationDialog dialog(&m_gamepad); + dialog.exec(); + + connect(&m_gamepad, &QGamepad::axisRightXChanged, this, &GamepadInputController::axisRightXChanged); + connect(&m_gamepad, &QGamepad::axisRightYChanged, this, &GamepadInputController::axisRightYChanged); + connect(&m_gamepad, &QGamepad::axisLeftXChanged, this, &GamepadInputController::axisLeftXChanged); + connect(&m_gamepad, &QGamepad::axisLeftYChanged, this, &GamepadInputController::axisLeftYChanged); +} + void GamepadInputController::axisRightXChanged(double value) { m_rightX = value; diff --git a/plugins/feature/gs232controller/gamepadinputcontroller.h b/plugins/feature/gs232controller/gamepadinputcontroller.h index d881b03d2..996d12f94 100644 --- a/plugins/feature/gs232controller/gamepadinputcontroller.h +++ b/plugins/feature/gs232controller/gamepadinputcontroller.h @@ -29,6 +29,8 @@ public: GamepadInputController(int deviceId); double getAxisValue(int axis) override; int getNumberOfAxes() const override; + bool supportsConfiguration() const override; + void configure() override; static QStringList getAllControllers(); static GamepadInputController* open(const QString& name); diff --git a/plugins/feature/gs232controller/gs232controllergui.cpp b/plugins/feature/gs232controller/gs232controllergui.cpp index 4dd44c30a..63e31bb7f 100644 --- a/plugins/feature/gs232controller/gs232controllergui.cpp +++ b/plugins/feature/gs232controller/gs232controllergui.cpp @@ -304,6 +304,8 @@ void GS232ControllerGUI::updateInputController() ui->inputSensitivityLabel->setEnabled(enabled); ui->inputSensitivity->setEnabled(enabled); ui->inputSensitivityText->setEnabled(enabled); + ui->inputConfigure->setEnabled(enabled); + ui->inputConfigure->setVisible(enabled); } void GS232ControllerGUI::checkInputController() @@ -383,6 +385,13 @@ void GS232ControllerGUI::on_inputSensitivty_valueChanged(int value) applySettings(); } +void GS232ControllerGUI::on_inputConfigure_clicked() +{ + if (m_inputController) { + m_inputController->configure(); + } +} + GS232ControllerGUI::~GS232ControllerGUI() { m_dfmStatusDialog.close(); @@ -949,6 +958,7 @@ void GS232ControllerGUI::makeUIConnections() QObject::connect(ui->coordinates, qOverload(&QComboBox::currentIndexChanged), this, &GS232ControllerGUI::on_coordinates_currentIndexChanged); QObject::connect(ui->inputController, qOverload(&QComboBox::currentIndexChanged), this, &GS232ControllerGUI::on_inputController_currentIndexChanged); QObject::connect(ui->inputSensitivity, qOverload(&QSlider::valueChanged), this, &GS232ControllerGUI::on_inputSensitivty_valueChanged); + QObject::connect(ui->inputConfigure, &QToolButton::clicked, this, &GS232ControllerGUI::on_inputConfigure_clicked); QObject::connect(ui->dfmTrack, &QToolButton::toggled, this, &GS232ControllerGUI::on_dfmTrack_clicked); QObject::connect(ui->dfmLubePumps, &QToolButton::toggled, this, &GS232ControllerGUI::on_dfmLubePumps_clicked); QObject::connect(ui->dfmBrakes, &QToolButton::toggled, this, &GS232ControllerGUI::on_dfmBrakes_clicked); diff --git a/plugins/feature/gs232controller/gs232controllergui.h b/plugins/feature/gs232controller/gs232controllergui.h index 55bf01612..61d5ebf0d 100644 --- a/plugins/feature/gs232controller/gs232controllergui.h +++ b/plugins/feature/gs232controller/gs232controllergui.h @@ -127,6 +127,7 @@ private slots: void updateStatus(); void on_inputController_currentIndexChanged(int index); void on_inputSensitivty_valueChanged(int value); + void on_inputConfigure_clicked(); void updateInputControllerList(); void checkInputController(); }; diff --git a/plugins/feature/gs232controller/gs232controllergui.ui b/plugins/feature/gs232controller/gs232controllergui.ui index 6edfed6f0..9cfd053dc 100644 --- a/plugins/feature/gs232controller/gs232controllergui.ui +++ b/plugins/feature/gs232controller/gs232controllergui.ui @@ -337,18 +337,6 @@ - - - - Gamepad / joystick to use - - - - None - - - - @@ -742,6 +730,32 @@ + + + + + + Gamepad / joystick to use + + + + None + + + + + + + + Configure input + + + C + + + + + diff --git a/plugins/feature/gs232controller/inputcontroller.h b/plugins/feature/gs232controller/inputcontroller.h index d35306c4b..56a0d66a5 100644 --- a/plugins/feature/gs232controller/inputcontroller.h +++ b/plugins/feature/gs232controller/inputcontroller.h @@ -29,6 +29,8 @@ public: // value returned should be current axis position in range [-1,1] virtual double getAxisValue(int axis) = 0; virtual int getNumberOfAxes() const = 0; + virtual bool supportsConfiguration() const { return false; } + virtual void configure() {}; }; diff --git a/plugins/feature/gs232controller/readme.md b/plugins/feature/gs232controller/readme.md index 34770c183..b25d66c88 100644 --- a/plugins/feature/gs232controller/readme.md +++ b/plugins/feature/gs232controller/readme.md @@ -5,7 +5,7 @@ The Rotator Controller feature plugin allows SDRangel to send commands to GS-232 and SPID rotators as well as hamlib's rotctld, via a serial or TCP connection. This allows SDRangel to point antennas mounted on a rotator to a specified azimuth and elevation. -Azimuth and elevation can be set manually by a user in the GUI, via the REST API, via another plugin, such as the Map Feature, the ADS-B Demodulator, or the Star Tracker, or by controller/gamepads (such as an XBox Controller). +Azimuth and elevation can be set manually by a user in the GUI, via the REST API, via another plugin, such as the Map Feature, the ADS-B Demodulator, or the Star Tracker, or by controller/gamepads (such as an XBox Wireless Controller).

Interface

@@ -109,13 +109,16 @@ Equations for translating between these coordinate systems can be found [here](h

22: Input Control

-Specifies a controller/gamepad (such as an XBox Wireless Controller) that can be used to specify target coordinates or azimuth and elevation offset. +Specifies a controller/gamepad that can be used to specify target coordinates or azimuth and elevation offset. -When a controller with 2 sticks (4 axes) such as the XBox Wireless Controller is used, the right stick is used for controlling target coordinates, +When a gamepad with 2 sticks (4 axes) such as the XBox Wireless Controller is used, the right stick is used for controlling target coordinates, while the left stick is for controlling azimuth and elevation offset. If a controller only has 2 axes, target coordinates will be controlled when not tracking (6) and offset will be controlled when tracking. The [Qt Gamepad](https://doc.qt.io/qt-5/qtgamepad-index.html) library is used to implement gamepad support. +On Linux, using Qt Gamepad with the evdev backend, all joysticks & gamepads appear as though they have 4 axes (a limitation of Qt Gamepad). +If using a joystick which only has 2 axes, whether it corresponds to the left or right stick can be configured by pressing the 'C' button. +On Linux, the [xone driver](https://github.com/medusalix/xone) has support for the Xbox Wireless Controller, that isn't supported by the older xpad driver that is included with Ubuntu.

23: Sensitivity

From fae899926f1c862717c3f34362d8db102f19f201 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Mon, 24 Apr 2023 12:11:31 +0100 Subject: [PATCH 4/7] Only show config button if supported --- .../gamepadconfigurationdialog.cpp | 104 ++++++++---------- .../gamepadconfigurationdialog.ui | 23 ++-- .../gs232controller/gs232controllergui.cpp | 3 +- 3 files changed, 57 insertions(+), 73 deletions(-) diff --git a/plugins/feature/gs232controller/gamepadconfigurationdialog.cpp b/plugins/feature/gs232controller/gamepadconfigurationdialog.cpp index 79abdcb4b..b4b0c886e 100644 --- a/plugins/feature/gs232controller/gamepadconfigurationdialog.cpp +++ b/plugins/feature/gs232controller/gamepadconfigurationdialog.cpp @@ -44,96 +44,80 @@ void GamepadConfigurationDialog::accept() void GamepadConfigurationDialog::on_config0_clicked() { - if (ui->config0->text() == "Configure") - { - ui->config0->setText("Done"); - ui->config1->setEnabled(false); - ui->config2->setEnabled(false); - ui->config3->setEnabled(false); - QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisRightX); - } - else - { - ui->config0->setText("Configure"); - ui->config1->setEnabled(true); - ui->config2->setEnabled(true); - ui->config3->setEnabled(true); - } + ui->config0->setText("Configuring"); + ui->config0->setEnabled(false); + ui->config1->setEnabled(false); + ui->config2->setEnabled(false); + ui->config3->setEnabled(false); + QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisRightX); } void GamepadConfigurationDialog::on_config1_clicked() { - if (ui->config1->text() == "Configure") - { - ui->config1->setText("Done"); - ui->config0->setEnabled(false); - ui->config2->setEnabled(false); - ui->config3->setEnabled(false); - QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisRightY); - } - else - { - ui->config1->setText("Configure"); - ui->config0->setEnabled(true); - ui->config2->setEnabled(true); - ui->config3->setEnabled(true); - } + ui->config1->setText("Configuring"); + ui->config0->setEnabled(false); + ui->config1->setEnabled(false); + ui->config2->setEnabled(false); + ui->config3->setEnabled(false); + QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisRightY); } void GamepadConfigurationDialog::on_config2_clicked() { - if (ui->config2->text() == "Configure") - { - ui->config2->setText("Done"); - ui->config0->setEnabled(false); - ui->config1->setEnabled(false); - ui->config3->setEnabled(false); - QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisLeftX); - } - else - { - ui->config2->setText("Configure"); - ui->config0->setEnabled(true); - ui->config1->setEnabled(true); - ui->config3->setEnabled(true); - } + ui->config2->setText("Configuring"); + ui->config0->setEnabled(false); + ui->config1->setEnabled(false); + ui->config2->setEnabled(false); + ui->config3->setEnabled(false); + QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisLeftX); } void GamepadConfigurationDialog::on_config3_clicked() { - if (ui->config3->text() == "Configure") - { - ui->config3->setText("Done"); - ui->config0->setEnabled(false); - ui->config1->setEnabled(false); - ui->config2->setEnabled(false); - QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisLeftY); - } - else - { - ui->config3->setText("Configure"); - ui->config0->setEnabled(true); - ui->config1->setEnabled(true); - ui->config2->setEnabled(true); - } + ui->config3->setText("Configuring"); + ui->config0->setEnabled(false); + ui->config1->setEnabled(false); + ui->config2->setEnabled(false); + ui->config3->setEnabled(false); + QGamepadManager::instance()->configureAxis(m_gamepad->deviceId(), QGamepadManager ::AxisLeftY); } void GamepadConfigurationDialog::axisRightXChanged(double value) { ui->value0->setText(QString::number(value)); + ui->config0->setText("Configure"); + ui->config0->setEnabled(true); + ui->config1->setEnabled(true); + ui->config2->setEnabled(true); + ui->config3->setEnabled(true); } void GamepadConfigurationDialog::axisRightYChanged(double value) { ui->value1->setText(QString::number(value)); + ui->config1->setText("Configure"); + ui->config0->setEnabled(true); + ui->config1->setEnabled(true); + ui->config2->setEnabled(true); + ui->config3->setEnabled(true); } void GamepadConfigurationDialog::axisLeftXChanged(double value) { ui->value2->setText(QString::number(value)); + ui->config2->setText("Configure"); + ui->config0->setEnabled(true); + ui->config1->setEnabled(true); + ui->config2->setEnabled(true); + ui->config3->setEnabled(true); } void GamepadConfigurationDialog::axisLeftYChanged(double value) { ui->value3->setText(QString::number(value)); + ui->config3->setText("Configure"); + ui->config0->setEnabled(true); + ui->config1->setEnabled(true); + ui->config2->setEnabled(true); + ui->config3->setEnabled(true); } diff --git a/plugins/feature/gs232controller/gamepadconfigurationdialog.ui b/plugins/feature/gs232controller/gamepadconfigurationdialog.ui index 34a83f83c..9c9ddcf65 100644 --- a/plugins/feature/gs232controller/gamepadconfigurationdialog.ui +++ b/plugins/feature/gs232controller/gamepadconfigurationdialog.ui @@ -54,7 +54,7 @@ - Azimuth Offset Axis + Elevation Offset Axis @@ -75,7 +75,7 @@ - Elevation Offset Axis + Azimuth Offset Axis @@ -113,7 +113,14 @@ - + + + + Configure + + + + @@ -125,15 +132,7 @@ To configure a controller axis: - press the Configure button, - - move the controller axis, - - then press Done - - - - - - - Configure + - move the controller axis you want for that function. diff --git a/plugins/feature/gs232controller/gs232controllergui.cpp b/plugins/feature/gs232controller/gs232controllergui.cpp index 63e31bb7f..f38df2d68 100644 --- a/plugins/feature/gs232controller/gs232controllergui.cpp +++ b/plugins/feature/gs232controller/gs232controllergui.cpp @@ -252,6 +252,7 @@ GS232ControllerGUI::GS232ControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featu m_settings.setRollupState(&m_rollupState); + ui->inputConfigure->setVisible(false); updateInputControllerList(); connect(InputControllerManager::instance(), &InputControllerManager::controllersChanged, this, &GS232ControllerGUI::updateInputControllerList); connect(&m_inputTimer, &QTimer::timeout, this, &GS232ControllerGUI::checkInputController); @@ -305,7 +306,7 @@ void GS232ControllerGUI::updateInputController() ui->inputSensitivity->setEnabled(enabled); ui->inputSensitivityText->setEnabled(enabled); ui->inputConfigure->setEnabled(enabled); - ui->inputConfigure->setVisible(enabled); + ui->inputConfigure->setVisible(enabled && m_inputController->supportsConfiguration()); } void GS232ControllerGUI::checkInputController() From d1e7d6f1e21c8b010452eed427ef98188be172f9 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Mon, 24 Apr 2023 12:14:59 +0100 Subject: [PATCH 5/7] Only show config button on Linux/Android --- plugins/feature/gs232controller/gamepadinputcontroller.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/feature/gs232controller/gamepadinputcontroller.cpp b/plugins/feature/gs232controller/gamepadinputcontroller.cpp index 3585446e2..eacffab9d 100644 --- a/plugins/feature/gs232controller/gamepadinputcontroller.cpp +++ b/plugins/feature/gs232controller/gamepadinputcontroller.cpp @@ -58,7 +58,11 @@ int GamepadInputController::getNumberOfAxes() const bool GamepadInputController::supportsConfiguration() const { // Should only return true on Linux evdev or Android - return QGamepadManager::instance()->isConfigurationNeeded(m_gamepad.deviceId()); +#if defined(LINUX) || defined(ANDROID) + return true; +#else + return false; +#endif } void GamepadInputController::configure() From ef76e3404075101b8029707764657787b270e63a Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Mon, 24 Apr 2023 12:42:13 +0100 Subject: [PATCH 6/7] Add gamepad dependency --- .appveyor.yml | 2 +- debian/control | 6 ++++-- plugins/feature/gs232controller/CMakeLists.txt | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 73d225a15..787fe0d40 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -84,7 +84,7 @@ for: qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick-window2 qml-module-qtquick-dialogs \ qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtgraphicaleffects \ libqt5serialport5-dev qtdeclarative5-dev qtpositioning5-dev qtlocation5-dev \ - libqt5charts5-dev libqt5texttospeech5-dev libfaad-dev zlib1g-dev \ + libqt5charts5-dev libqt5texttospeech5-dev libqt5gamepad5-dev libfaad-dev zlib1g-dev \ libusb-1.0-0-dev libhidapi-dev libboost-all-dev libasound2-dev libopencv-dev libopencv-imgcodecs-dev \ libxml2-dev bison flex ffmpeg libpostproc-dev libavcodec-dev libavformat-dev \ libopus-dev libcodec2-dev libairspy-dev libhackrf-dev \ diff --git a/debian/control b/debian/control index 82eb6b752..b080b95e8 100644 --- a/debian/control +++ b/debian/control @@ -16,6 +16,7 @@ Build-Depends: debhelper (>= 9), libqt5websockets5-dev, libqt5quick5, libqt5texttospeech5-dev, + libqt5gamepad5-dev, qml-module-qtlocation, qml-module-qtpositioning, qml-module-qtquick-window2, @@ -61,6 +62,7 @@ Depends: ${shlibs:Depends}, libasound2, libgl1-mesa-glx, libqt5multimedia5-plugins, + libqt5gamepad5, qtspeech5-speechd-plugin, pulseaudio, ffmpeg, @@ -80,8 +82,8 @@ Description: SDR/Analyzer/Generator front-end for various hardware Based on Qt5 framework and OpenGL for the spectrum and scope rendering. Builds on Linux, Windows and Mac O/S Reception modes supported: - Analog: AM, ATV, NFM, WFM, SSB, broadcast FM, APT - Digital: D-Star, Yaesu SF, DMR, dPMR, FreeDV, DAB, DVB-S, LoRa, ADS-B, Packet (AX.25/APRS), AIS + Analog: AM, ATV, NFM, WFM, SSB, broadcast FM, APT, ILS, VOR + Digital: D-Star, Yaesu SF, DMR, dPMR, FreeDV, M17, DAB, DVB-S, LoRa, ADS-B, Packet (AX.25/APRS), AIS, FT8, Navtex, Radiosonde, RTTY, Pager Analyzer: Generic channel Transmission modes supported: Analog: AM, ATV, NFM, SSB, WFM diff --git a/plugins/feature/gs232controller/CMakeLists.txt b/plugins/feature/gs232controller/CMakeLists.txt index 9961e3e43..c849be982 100644 --- a/plugins/feature/gs232controller/CMakeLists.txt +++ b/plugins/feature/gs232controller/CMakeLists.txt @@ -88,7 +88,7 @@ target_link_libraries(${TARGET_NAME} install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) if(WIN32) - # Run deployqt for serial libraries + # Run deployqt for serial & gamepad libraries include(DeployQt) windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/../../../sdrgui/resources) endif() From 4c15e28922456d9050291f8d52dbb2a63eda0a2c Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Mon, 24 Apr 2023 13:16:20 +0100 Subject: [PATCH 7/7] Add Gamepad dependency for Android --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 78980d373..88619d1bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -635,7 +635,9 @@ if (BUILD_GUI) Location TextToSpeech Svg - AndroidExtras) + AndroidExtras + OPTIONAL_COMPONENTS + Gamepad) endif() else() find_package(OpenGL REQUIRED)