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;