Rotator Controller: Add gamepad input controller support.

This commit is contained in:
Jon Beniston 2023-04-23 19:52:02 +01:00
parent fc923ebbed
commit a1c1308580
16 changed files with 874 additions and 209 deletions

View File

@ -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

View File

@ -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 "")

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QGamepadManager>
#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<int> 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<int> 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_GAMEPADINPUTCONTROLLER_H_
#define INCLUDE_FEATURE_GAMEPADINPUTCONTROLLER_H_
#include "inputcontroller.h"
#include <QGamepad>
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_

View File

@ -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<QString>& 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));
}

View File

@ -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<double>(&QDoubleSpinBox::valueChanged), this, &GS232ControllerGUI::on_tolerance_valueChanged);
QObject::connect(ui->precision, qOverload<int>(&QSpinBox::valueChanged), this, &GS232ControllerGUI::on_precision_valueChanged);
QObject::connect(ui->coordinates, qOverload<int>(&QComboBox::currentIndexChanged), this, &GS232ControllerGUI::on_coordinates_currentIndexChanged);
QObject::connect(ui->inputController, qOverload<int>(&QComboBox::currentIndexChanged), this, &GS232ControllerGUI::on_inputController_currentIndexChanged);
QObject::connect(ui->inputSensitivity, qOverload<int>(&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);

View File

@ -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_

View File

@ -306,7 +306,7 @@
<x>10</x>
<y>140</y>
<width>341</width>
<height>191</height>
<height>201</height>
</rect>
</property>
<property name="windowTitle">
@ -330,6 +330,84 @@
</property>
<item>
<layout class="QGridLayout" name="settingsGridLayout">
<item row="1" column="2">
<widget class="QLabel" name="baudRateLabel">
<property name="text">
<string>Baud rate</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QComboBox" name="inputController">
<property name="toolTip">
<string>Gamepad / joystick to use</string>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="azimuthMin">
<property name="maximum">
<number>450</number>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="elevationOffsetLabel">
<property name="text">
<string>Elevation offset</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="azimuthMaxLabel">
<property name="text">
<string>Azimuth max</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Coordinates</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="azimuthMinLabel">
<property name="text">
<string>Azimuth min</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QSpinBox" name="elevationMax">
<property name="maximum">
<number>180</number>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QLabel" name="inputSensitivityLabel">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Sensitivity</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="elevationMaxLabel">
<property name="text">
<string>Elevation max</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="azimuthOffsetLabel">
<property name="text">
@ -337,6 +415,56 @@
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QSpinBox" name="elevationOffset">
<property name="toolTip">
<string>Specify an offset angle in degrees that will be added to the target elevation to correct for misalignment</string>
</property>
<property name="minimum">
<number>-180</number>
</property>
<property name="maximum">
<number>180</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="protocol">
<property name="toolTip">
<string>Command protocol</string>
</property>
<item>
<property name="text">
<string>GS-232</string>
</property>
</item>
<item>
<property name="text">
<string>SPID</string>
</property>
</item>
<item>
<property name="text">
<string>rotctld</string>
</property>
</item>
<item>
<property name="text">
<string>DFM</string>
</property>
</item>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="portLabel">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QSpinBox" name="precision">
<property name="enabled">
@ -353,64 +481,6 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="host">
<property name="toolTip">
<string>Hostname / IP address of computer to connect to</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QSpinBox" name="azimuthMax">
<property name="maximum">
<number>450</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="toleranceLabel">
<property name="text">
<string>Tolerance</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="serialPort">
<property name="toolTip">
<string>Name of serial port to use to connect to the rotator</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="azimuthOffset">
<property name="toolTip">
<string>Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment</string>
</property>
<property name="minimum">
<number>-360</number>
</property>
<property name="maximum">
<number>360</number>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="azimuthMaxLabel">
<property name="text">
<string>Azimuth max</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="connectionLabel">
<property name="text">
<string>Connection</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QSpinBox" name="port">
<property name="toolTip">
@ -421,110 +491,13 @@
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="baudRateLabel">
<property name="text">
<string>Baud rate</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="connection">
<property name="toolTip">
<string>The type of connection to use to the rotator</string>
</property>
<item>
<property name="text">
<string>Serial</string>
</property>
</item>
<item>
<property name="text">
<string>TCP</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="serialPortLabel">
<property name="text">
<string>Serial Port</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QDoubleSpinBox" name="tolerance">
<property name="toolTip">
<string>Tolerance in degrees</string>
</property>
<property name="decimals">
<number>0</number>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="azimuthMin">
<item row="4" column="3">
<widget class="QSpinBox" name="azimuthMax">
<property name="maximum">
<number>450</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="hostLabel">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="portLabel">
<property name="text">
<string>Port</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="elevationMaxLabel">
<property name="text">
<string>Elevation max</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="elevationMinLabel">
<property name="text">
<string>Elevation min</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QSpinBox" name="elevationMax">
<property name="maximum">
<number>180</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="azimuthMinLabel">
<property name="text">
<string>Azimuth min</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="elevationOffsetLabel">
<property name="text">
<string>Elevation offset</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="elevationMin">
<property name="maximum">
<number>180</number>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="baudRate">
<property name="toolTip">
@ -585,6 +558,61 @@
</item>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="connectionLabel">
<property name="text">
<string>Connection</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="connection">
<property name="toolTip">
<string>The type of connection to use to the rotator</string>
</property>
<item>
<property name="text">
<string>Serial</string>
</property>
</item>
<item>
<property name="text">
<string>TCP</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="serialPort">
<property name="toolTip">
<string>Name of serial port to use to connect to the rotator</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="hostLabel">
<property name="text">
<string>Host</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="elevationMin">
<property name="maximum">
<number>180</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="host">
<property name="toolTip">
<string>Hostname / IP address of computer to connect to</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLabel" name="precisionLabel">
<property name="enabled">
@ -595,49 +623,6 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="protocol">
<property name="toolTip">
<string>Command protocol</string>
</property>
<item>
<property name="text">
<string>GS-232</string>
</property>
</item>
<item>
<property name="text">
<string>SPID</string>
</property>
</item>
<item>
<property name="text">
<string>rotctld</string>
</property>
</item>
<item>
<property name="text">
<string>DFM</string>
</property>
</item>
</widget>
</item>
<item row="3" column="3">
<widget class="QSpinBox" name="elevationOffset">
<property name="toolTip">
<string>Specify an offset angle in degrees that will be added to the target elevation to correct for misalignment</string>
</property>
<property name="minimum">
<number>-180</number>
</property>
<property name="maximum">
<number>180</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="protocolLabel">
<property name="text">
@ -645,6 +630,50 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="elevationMinLabel">
<property name="text">
<string>Elevation min</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="azimuthOffset">
<property name="toolTip">
<string>Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment</string>
</property>
<property name="minimum">
<number>-360</number>
</property>
<property name="maximum">
<number>360</number>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QDoubleSpinBox" name="tolerance">
<property name="toolTip">
<string>Tolerance in degrees</string>
</property>
<property name="decimals">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="serialPortLabel">
<property name="text">
<string>Serial Port</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="toleranceLabel">
<property name="text">
<string>Tolerance</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="coordinates">
<property name="toolTip">
@ -667,13 +696,52 @@
</item>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label">
<item row="8" column="0">
<widget class="QLabel" name="inputControllerLabel">
<property name="text">
<string>Coordinates</string>
<string>Input Control</string>
</property>
</widget>
</item>
<item row="8" column="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSlider" name="inputSensitivity">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Input controller sensitivity</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>2000</number>
</property>
<property name="pageStep">
<number>10</number>
</property>
<property name="value">
<number>25</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="inputSensitivityText">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>100%</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>

View File

@ -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());
}

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifdef QT_GAMEPAD_FOUND
#include <QGamepadManager>
#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();
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_INPUTCONTROLLER_H_
#define INCLUDE_FEATURE_INPUTCONTROLLER_H_
#include <QObject>
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_

View File

@ -2,10 +2,10 @@
<h2>Introduction</h2>
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).
<h2>Interface</h2>
@ -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).
<h3>22: Input Control</h3>
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.
<h3>23: Sensitivity</h3>
Specifies the sensitivity on the input controls (22). The higher the value, the faster coordinates will change for a given control stick movement.
<h2>Protocol Implementations</h2>
<h3>GS-232 Protocol Implementation Notes</h3>

View File

@ -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:

View File

@ -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;
}

View File

@ -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;