parent
4083b4a6f7
commit
3b0f1f8718
@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
set(EXTRA_SRC)
|
||||||
|
|
||||||
|
if (ANDROID)
|
||||||
|
list(APPEND EXTRA_SRC
|
||||||
|
JoystickAndroid.cc
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(Joystick
|
||||||
|
Joystick.cc
|
||||||
|
JoystickManager.cc
|
||||||
|
JoystickSDL.cc
|
||||||
|
JoystickMavCommand.cc
|
||||||
|
${EXTRA_SRC}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(Joystick
|
||||||
|
PRIVATE
|
||||||
|
ui
|
||||||
|
PUBLIC
|
||||||
|
qgc
|
||||||
|
ui
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(Joystick PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(Joystick PUBLIC sdl2)
|
||||||
|
else()
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
if (IS_DIRECTORY "${SDL2_INCLUDE_DIRS}")
|
||||||
|
include_directories(${SDL2_INCLUDE_DIRS})
|
||||||
|
string(STRIP "${SDL2_LIBRARIES}" SDL2_LIBRARIES)
|
||||||
|
target_link_libraries(Joystick PRIVATE ${SDL2_LIBRARIES})
|
||||||
|
else()
|
||||||
|
include_directories(${SDL2_DIR})
|
||||||
|
target_link_libraries(Joystick PRIVATE SDL2::SDL2)
|
||||||
|
endif()
|
||||||
|
endif()
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,370 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
*
|
||||||
|
* QGroundControl is licensed according to the terms in the file
|
||||||
|
* COPYING.md in the root of the source code directory.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/// @file
|
||||||
|
/// @brief Joystick Controller
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "QGCLoggingCategory.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
#include "MultiVehicleManager.h"
|
||||||
|
#include "JoystickMavCommand.h"
|
||||||
|
|
||||||
|
// JoystickLog Category declaration moved to QGCLoggingCategory.cc to allow access in Vehicle
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(JoystickValuesLog)
|
||||||
|
Q_DECLARE_METATYPE(GRIPPER_ACTIONS)
|
||||||
|
|
||||||
|
/// Action assigned to button
|
||||||
|
class AssignedButtonAction : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
AssignedButtonAction(QObject* parent, const QString name);
|
||||||
|
QString action;
|
||||||
|
QElapsedTimer buttonTime;
|
||||||
|
bool repeat = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Assignable Button Action
|
||||||
|
class AssignableButtonAction : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
AssignableButtonAction(QObject* parent, QString action_, bool canRepeat_ = false);
|
||||||
|
Q_PROPERTY(QString action READ action CONSTANT)
|
||||||
|
Q_PROPERTY(bool canRepeat READ canRepeat CONSTANT)
|
||||||
|
QString action () { return _action; }
|
||||||
|
bool canRepeat () const{ return _repeat; }
|
||||||
|
private:
|
||||||
|
QString _action;
|
||||||
|
bool _repeat = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Joystick Controller
|
||||||
|
class Joystick : public QThread
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Joystick(const QString& name, int axisCount, int buttonCount, int hatCount, MultiVehicleManager* multiVehicleManager);
|
||||||
|
|
||||||
|
virtual ~Joystick();
|
||||||
|
|
||||||
|
typedef struct Calibration_t {
|
||||||
|
int min;
|
||||||
|
int max;
|
||||||
|
int center;
|
||||||
|
int deadband;
|
||||||
|
bool reversed;
|
||||||
|
Calibration_t()
|
||||||
|
: min(-32767)
|
||||||
|
, max(32767)
|
||||||
|
, center(0)
|
||||||
|
, deadband(0)
|
||||||
|
, reversed(false) {}
|
||||||
|
} Calibration_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
rollFunction,
|
||||||
|
pitchFunction,
|
||||||
|
yawFunction,
|
||||||
|
throttleFunction,
|
||||||
|
gimbalPitchFunction,
|
||||||
|
gimbalYawFunction,
|
||||||
|
maxFunction
|
||||||
|
} AxisFunction_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ThrottleModeCenterZero,
|
||||||
|
ThrottleModeDownZero,
|
||||||
|
ThrottleModeMax
|
||||||
|
} ThrottleMode_t;
|
||||||
|
|
||||||
|
Q_PROPERTY(QString name READ name CONSTANT)
|
||||||
|
Q_PROPERTY(bool calibrated MEMBER _calibrated NOTIFY calibratedChanged)
|
||||||
|
Q_PROPERTY(int totalButtonCount READ totalButtonCount CONSTANT)
|
||||||
|
Q_PROPERTY(int axisCount READ axisCount CONSTANT)
|
||||||
|
Q_PROPERTY(bool requiresCalibration READ requiresCalibration CONSTANT)
|
||||||
|
|
||||||
|
//-- Actions assigned to buttons
|
||||||
|
Q_PROPERTY(QStringList buttonActions READ buttonActions NOTIFY buttonActionsChanged)
|
||||||
|
|
||||||
|
//-- Actions that can be assigned to buttons
|
||||||
|
Q_PROPERTY(QmlObjectListModel* assignableActions READ assignableActions NOTIFY assignableActionsChanged)
|
||||||
|
Q_PROPERTY(QStringList assignableActionTitles READ assignableActionTitles NOTIFY assignableActionsChanged)
|
||||||
|
Q_PROPERTY(QString disabledActionName READ disabledActionName CONSTANT)
|
||||||
|
|
||||||
|
Q_PROPERTY(int throttleMode READ throttleMode WRITE setThrottleMode NOTIFY throttleModeChanged)
|
||||||
|
Q_PROPERTY(float axisFrequencyHz READ axisFrequencyHz WRITE setAxisFrequency NOTIFY axisFrequencyHzChanged)
|
||||||
|
Q_PROPERTY(float minAxisFrequencyHz MEMBER _minAxisFrequencyHz CONSTANT)
|
||||||
|
Q_PROPERTY(float maxAxisFrequencyHz MEMBER _minAxisFrequencyHz CONSTANT)
|
||||||
|
Q_PROPERTY(float buttonFrequencyHz READ buttonFrequencyHz WRITE setButtonFrequency NOTIFY buttonFrequencyHzChanged)
|
||||||
|
Q_PROPERTY(float minButtonFrequencyHz MEMBER _minButtonFrequencyHz CONSTANT)
|
||||||
|
Q_PROPERTY(float maxButtonFrequencyHz MEMBER _maxButtonFrequencyHz CONSTANT)
|
||||||
|
Q_PROPERTY(bool negativeThrust READ negativeThrust WRITE setNegativeThrust NOTIFY negativeThrustChanged)
|
||||||
|
Q_PROPERTY(float exponential READ exponential WRITE setExponential NOTIFY exponentialChanged)
|
||||||
|
Q_PROPERTY(bool accumulator READ accumulator WRITE setAccumulator NOTIFY accumulatorChanged)
|
||||||
|
Q_PROPERTY(bool circleCorrection READ circleCorrection WRITE setCircleCorrection NOTIFY circleCorrectionChanged)
|
||||||
|
|
||||||
|
Q_INVOKABLE void setButtonRepeat (int button, bool repeat);
|
||||||
|
Q_INVOKABLE bool getButtonRepeat (int button);
|
||||||
|
Q_INVOKABLE void setButtonAction (int button, const QString& action);
|
||||||
|
Q_INVOKABLE QString getButtonAction (int button);
|
||||||
|
|
||||||
|
// Property accessors
|
||||||
|
|
||||||
|
QString name () { return _name; }
|
||||||
|
int totalButtonCount () const{ return _totalButtonCount; }
|
||||||
|
int axisCount () const{ return _axisCount; }
|
||||||
|
QStringList buttonActions ();
|
||||||
|
|
||||||
|
QmlObjectListModel* assignableActions () { return &_assignableButtonActions; }
|
||||||
|
QStringList assignableActionTitles () { return _availableActionTitles; }
|
||||||
|
QString disabledActionName () { return _buttonActionNone; }
|
||||||
|
|
||||||
|
/// Start the polling thread which will in turn emit joystick signals
|
||||||
|
void startPolling(Vehicle* vehicle);
|
||||||
|
void stopPolling(void);
|
||||||
|
|
||||||
|
void setCalibration(int axis, Calibration_t& calibration);
|
||||||
|
Calibration_t getCalibration(int axis);
|
||||||
|
|
||||||
|
void setFunctionAxis(AxisFunction_t function, int axis);
|
||||||
|
int getFunctionAxis(AxisFunction_t function);
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Joystick index used by sdl library
|
||||||
|
// Settable because sdl library remaps indices after certain events
|
||||||
|
virtual int index(void) = 0;
|
||||||
|
virtual void setIndex(int index) = 0;
|
||||||
|
*/
|
||||||
|
virtual bool requiresCalibration(void) { return true; }
|
||||||
|
|
||||||
|
int throttleMode ();
|
||||||
|
void setThrottleMode (int mode);
|
||||||
|
|
||||||
|
bool negativeThrust () const;
|
||||||
|
void setNegativeThrust (bool allowNegative);
|
||||||
|
|
||||||
|
float exponential () const;
|
||||||
|
void setExponential (float expo);
|
||||||
|
|
||||||
|
bool accumulator () const;
|
||||||
|
void setAccumulator (bool accu);
|
||||||
|
|
||||||
|
bool deadband () const;
|
||||||
|
void setDeadband (bool accu);
|
||||||
|
|
||||||
|
bool circleCorrection () const;
|
||||||
|
void setCircleCorrection(bool circleCorrection);
|
||||||
|
|
||||||
|
void setTXMode (int mode);
|
||||||
|
int getTXMode () { return _transmitterMode; }
|
||||||
|
|
||||||
|
/// Set the current calibration mode
|
||||||
|
void setCalibrationMode (bool calibrating);
|
||||||
|
|
||||||
|
/// Get joystick message rate (in Hz)
|
||||||
|
float axisFrequencyHz () const{ return _axisFrequencyHz; }
|
||||||
|
/// Set joystick message rate (in Hz)
|
||||||
|
void setAxisFrequency (float val);
|
||||||
|
|
||||||
|
/// Get joystick button repeat rate (in Hz)
|
||||||
|
float buttonFrequencyHz () const{ return _buttonFrequencyHz; }
|
||||||
|
/// Set joystick button repeat rate (in Hz)
|
||||||
|
void setButtonFrequency(float val);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// The raw signals are only meant for use by calibration
|
||||||
|
void rawAxisValueChanged (int index, int value);
|
||||||
|
void rawButtonPressedChanged (int index, int pressed);
|
||||||
|
void calibratedChanged (bool calibrated);
|
||||||
|
void buttonActionsChanged ();
|
||||||
|
void assignableActionsChanged ();
|
||||||
|
void throttleModeChanged (int mode);
|
||||||
|
void negativeThrustChanged (bool allowNegative);
|
||||||
|
void exponentialChanged (float exponential);
|
||||||
|
void accumulatorChanged (bool accumulator);
|
||||||
|
void enabledChanged (bool enabled);
|
||||||
|
void circleCorrectionChanged (bool circleCorrection);
|
||||||
|
void axisValues (float roll, float pitch, float yaw, float throttle);
|
||||||
|
|
||||||
|
void axisFrequencyHzChanged ();
|
||||||
|
void buttonFrequencyHzChanged ();
|
||||||
|
void startContinuousZoom (int direction);
|
||||||
|
void stopContinuousZoom ();
|
||||||
|
void stepZoom (int direction);
|
||||||
|
void stepCamera (int direction);
|
||||||
|
void stepStream (int direction);
|
||||||
|
void triggerCamera ();
|
||||||
|
void startVideoRecord ();
|
||||||
|
void stopVideoRecord ();
|
||||||
|
void toggleVideoRecord ();
|
||||||
|
void gimbalPitchStep (int direction);
|
||||||
|
void gimbalYawStep (int direction);
|
||||||
|
void centerGimbal ();
|
||||||
|
void gimbalControlValue (double pitch, double yaw);
|
||||||
|
void setArmed (bool arm);
|
||||||
|
void setVtolInFwdFlight (bool set);
|
||||||
|
void setFlightMode (const QString& flightMode);
|
||||||
|
void emergencyStop ();
|
||||||
|
/**
|
||||||
|
* @brief Send MAV_CMD_DO_GRIPPER command to the vehicle
|
||||||
|
*
|
||||||
|
* @param gripperAction (Open / Close) Gripper action to command
|
||||||
|
*/
|
||||||
|
void gripperAction (GRIPPER_ACTIONS gripperAction);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void _setDefaultCalibration ();
|
||||||
|
void _saveSettings ();
|
||||||
|
void _saveButtonSettings ();
|
||||||
|
void _loadSettings ();
|
||||||
|
float _adjustRange (int value, Calibration_t calibration, bool withDeadbands);
|
||||||
|
void _executeButtonAction (const QString& action, bool buttonDown);
|
||||||
|
int _findAssignableButtonAction(const QString& action);
|
||||||
|
bool _validAxis (int axis) const;
|
||||||
|
bool _validButton (int button) const;
|
||||||
|
void _handleAxis ();
|
||||||
|
void _handleButtons ();
|
||||||
|
void _buildActionList (Vehicle* activeVehicle);
|
||||||
|
|
||||||
|
void _pitchStep (int direction);
|
||||||
|
void _yawStep (int direction);
|
||||||
|
double _localYaw = 0.0;
|
||||||
|
double _localPitch = 0.0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual bool _open () = 0;
|
||||||
|
virtual void _close () = 0;
|
||||||
|
virtual bool _update () = 0;
|
||||||
|
|
||||||
|
virtual bool _getButton (int i) = 0;
|
||||||
|
virtual int _getAxis (int i) = 0;
|
||||||
|
virtual bool _getHat (int hat,int i) = 0;
|
||||||
|
|
||||||
|
void _updateTXModeSettingsKey(Vehicle* activeVehicle);
|
||||||
|
int _mapFunctionMode(int mode, int function);
|
||||||
|
void _remapAxes(int currentMode, int newMode, int (&newMapping)[maxFunction]);
|
||||||
|
|
||||||
|
// Override from QThread
|
||||||
|
virtual void run();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
enum {
|
||||||
|
BUTTON_UP,
|
||||||
|
BUTTON_DOWN,
|
||||||
|
BUTTON_REPEAT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const float _defaultAxisFrequencyHz;
|
||||||
|
static const float _defaultButtonFrequencyHz;
|
||||||
|
|
||||||
|
uint8_t*_rgButtonValues = nullptr;
|
||||||
|
|
||||||
|
std::atomic<bool> _exitThread{false}; ///< true: signal thread to exit
|
||||||
|
bool _calibrationMode = false;
|
||||||
|
int* _rgAxisValues = nullptr;
|
||||||
|
Calibration_t* _rgCalibration = nullptr;
|
||||||
|
ThrottleMode_t _throttleMode = ThrottleModeDownZero;
|
||||||
|
bool _negativeThrust = false;
|
||||||
|
float _exponential = 0;
|
||||||
|
bool _accumulator = false;
|
||||||
|
bool _deadband = false;
|
||||||
|
bool _circleCorrection = true;
|
||||||
|
float _axisFrequencyHz = _defaultAxisFrequencyHz;
|
||||||
|
float _buttonFrequencyHz = _defaultButtonFrequencyHz;
|
||||||
|
Vehicle* _activeVehicle = nullptr;
|
||||||
|
|
||||||
|
bool _pollingStartedForCalibration = false;
|
||||||
|
|
||||||
|
QString _name;
|
||||||
|
bool _calibrated;
|
||||||
|
int _axisCount;
|
||||||
|
int _buttonCount;
|
||||||
|
int _hatCount;
|
||||||
|
int _hatButtonCount;
|
||||||
|
int _totalButtonCount;
|
||||||
|
|
||||||
|
static int _transmitterMode;
|
||||||
|
int _rgFunctionAxis[maxFunction] = {};
|
||||||
|
QElapsedTimer _axisTime;
|
||||||
|
|
||||||
|
QmlObjectListModel _assignableButtonActions;
|
||||||
|
QList<AssignedButtonAction*> _buttonActionArray;
|
||||||
|
QStringList _availableActionTitles;
|
||||||
|
MultiVehicleManager* _multiVehicleManager = nullptr;
|
||||||
|
|
||||||
|
QList<JoystickMavCommand> _customMavCommands;
|
||||||
|
|
||||||
|
static const float _minAxisFrequencyHz;
|
||||||
|
static const float _maxAxisFrequencyHz;
|
||||||
|
static const float _minButtonFrequencyHz;
|
||||||
|
static const float _maxButtonFrequencyHz;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const char* _rgFunctionSettingsKey[maxFunction];
|
||||||
|
|
||||||
|
static const char* _settingsGroup;
|
||||||
|
static const char* _calibratedSettingsKey;
|
||||||
|
static const char* _buttonActionNameKey;
|
||||||
|
static const char* _buttonActionRepeatKey;
|
||||||
|
static const char* _throttleModeSettingsKey;
|
||||||
|
static const char* _negativeThrustSettingsKey;
|
||||||
|
static const char* _exponentialSettingsKey;
|
||||||
|
static const char* _accumulatorSettingsKey;
|
||||||
|
static const char* _deadbandSettingsKey;
|
||||||
|
static const char* _circleCorrectionSettingsKey;
|
||||||
|
static const char* _axisFrequencySettingsKey;
|
||||||
|
static const char* _buttonFrequencySettingsKey;
|
||||||
|
static const char* _txModeSettingsKey;
|
||||||
|
static const char* _fixedWingTXModeSettingsKey;
|
||||||
|
static const char* _multiRotorTXModeSettingsKey;
|
||||||
|
static const char* _roverTXModeSettingsKey;
|
||||||
|
static const char* _vtolTXModeSettingsKey;
|
||||||
|
static const char* _submarineTXModeSettingsKey;
|
||||||
|
|
||||||
|
static const char* _buttonActionNone;
|
||||||
|
static const char* _buttonActionArm;
|
||||||
|
static const char* _buttonActionDisarm;
|
||||||
|
static const char* _buttonActionToggleArm;
|
||||||
|
static const char* _buttonActionVTOLFixedWing;
|
||||||
|
static const char* _buttonActionVTOLMultiRotor;
|
||||||
|
static const char* _buttonActionStepZoomIn;
|
||||||
|
static const char* _buttonActionStepZoomOut;
|
||||||
|
static const char* _buttonActionContinuousZoomIn;
|
||||||
|
static const char* _buttonActionContinuousZoomOut;
|
||||||
|
static const char* _buttonActionNextStream;
|
||||||
|
static const char* _buttonActionPreviousStream;
|
||||||
|
static const char* _buttonActionNextCamera;
|
||||||
|
static const char* _buttonActionPreviousCamera;
|
||||||
|
static const char* _buttonActionTriggerCamera;
|
||||||
|
static const char* _buttonActionStartVideoRecord;
|
||||||
|
static const char* _buttonActionStopVideoRecord;
|
||||||
|
static const char* _buttonActionToggleVideoRecord;
|
||||||
|
static const char* _buttonActionGimbalDown;
|
||||||
|
static const char* _buttonActionGimbalUp;
|
||||||
|
static const char* _buttonActionGimbalLeft;
|
||||||
|
static const char* _buttonActionGimbalRight;
|
||||||
|
static const char* _buttonActionGimbalCenter;
|
||||||
|
static const char* _buttonActionEmergencyStop;
|
||||||
|
static const char* _buttonActionGripperGrab;
|
||||||
|
static const char* _buttonActionGripperRelease;
|
||||||
|
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void _activeVehicleChanged(Vehicle* activeVehicle);
|
||||||
|
void _vehicleCountChanged(int count);
|
||||||
|
void _flightModesChanged();
|
||||||
|
};
|
@ -0,0 +1,293 @@
|
|||||||
|
#include "JoystickAndroid.h"
|
||||||
|
|
||||||
|
#include "QGCApplication.h"
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
int JoystickAndroid::_androidBtnListCount;
|
||||||
|
int *JoystickAndroid::_androidBtnList;
|
||||||
|
int JoystickAndroid::ACTION_DOWN;
|
||||||
|
int JoystickAndroid::ACTION_UP;
|
||||||
|
QMutex JoystickAndroid::m_mutex;
|
||||||
|
|
||||||
|
static void clear_jni_exception()
|
||||||
|
{
|
||||||
|
QAndroidJniEnvironment jniEnv;
|
||||||
|
if (jniEnv->ExceptionCheck()) {
|
||||||
|
jniEnv->ExceptionDescribe();
|
||||||
|
jniEnv->ExceptionClear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JoystickAndroid::JoystickAndroid(const QString& name, int axisCount, int buttonCount, int id, MultiVehicleManager* multiVehicleManager)
|
||||||
|
: Joystick(name,axisCount,buttonCount,0,multiVehicleManager)
|
||||||
|
, deviceId(id)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
QAndroidJniEnvironment env;
|
||||||
|
QAndroidJniObject inputDevice = QAndroidJniObject::callStaticObjectMethod("android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;", id);
|
||||||
|
|
||||||
|
//set button mapping (number->code)
|
||||||
|
jintArray b = env->NewIntArray(_androidBtnListCount);
|
||||||
|
env->SetIntArrayRegion(b,0,_androidBtnListCount,_androidBtnList);
|
||||||
|
|
||||||
|
QAndroidJniObject btns = inputDevice.callObjectMethod("hasKeys", "([I)[Z", b);
|
||||||
|
jbooleanArray jSupportedButtons = btns.object<jbooleanArray>();
|
||||||
|
jboolean* supportedButtons = env->GetBooleanArrayElements(jSupportedButtons, nullptr);
|
||||||
|
//create a mapping table (btnCode) that maps button number with button code
|
||||||
|
btnValue = new bool[_buttonCount];
|
||||||
|
btnCode = new int[_buttonCount];
|
||||||
|
int c = 0;
|
||||||
|
for (i = 0; i < _androidBtnListCount; i++) {
|
||||||
|
if (supportedButtons[i]) {
|
||||||
|
btnValue[c] = false;
|
||||||
|
btnCode[c] = _androidBtnList[i];
|
||||||
|
c++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env->ReleaseBooleanArrayElements(jSupportedButtons, supportedButtons, 0);
|
||||||
|
|
||||||
|
// set axis mapping (number->code)
|
||||||
|
axisValue = new int[_axisCount];
|
||||||
|
axisCode = new int[_axisCount];
|
||||||
|
QAndroidJniObject rangeListNative = inputDevice.callObjectMethod("getMotionRanges", "()Ljava/util/List;");
|
||||||
|
for (i = 0; i < _axisCount; i++) {
|
||||||
|
QAndroidJniObject range = rangeListNative.callObjectMethod("get", "(I)Ljava/lang/Object;",i);
|
||||||
|
axisCode[i] = range.callMethod<jint>("getAxis");
|
||||||
|
// Don't allow two axis with the same code
|
||||||
|
for (int j = 0; j < i; j++) {
|
||||||
|
if (axisCode[i] == axisCode[j]) {
|
||||||
|
axisCode[i] = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
axisValue[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(JoystickLog) << "axis:" <<_axisCount << "buttons:" <<_buttonCount;
|
||||||
|
QtAndroidPrivate::registerGenericMotionEventListener(this);
|
||||||
|
QtAndroidPrivate::registerKeyEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
JoystickAndroid::~JoystickAndroid() {
|
||||||
|
delete btnCode;
|
||||||
|
delete axisCode;
|
||||||
|
delete btnValue;
|
||||||
|
delete axisValue;
|
||||||
|
|
||||||
|
QtAndroidPrivate::unregisterGenericMotionEventListener(this);
|
||||||
|
QtAndroidPrivate::unregisterKeyEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QMap<QString, Joystick*> JoystickAndroid::discover(MultiVehicleManager* _multiVehicleManager) {
|
||||||
|
static QMap<QString, Joystick*> ret;
|
||||||
|
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
|
||||||
|
QAndroidJniEnvironment env;
|
||||||
|
QAndroidJniObject o = QAndroidJniObject::callStaticObjectMethod<jintArray>("android/view/InputDevice", "getDeviceIds");
|
||||||
|
jintArray jarr = o.object<jintArray>();
|
||||||
|
int sz = env->GetArrayLength(jarr);
|
||||||
|
jint *buff = env->GetIntArrayElements(jarr, nullptr);
|
||||||
|
|
||||||
|
int SOURCE_GAMEPAD = QAndroidJniObject::getStaticField<jint>("android/view/InputDevice", "SOURCE_GAMEPAD");
|
||||||
|
int SOURCE_JOYSTICK = QAndroidJniObject::getStaticField<jint>("android/view/InputDevice", "SOURCE_JOYSTICK");
|
||||||
|
|
||||||
|
QList<QString> names;
|
||||||
|
|
||||||
|
for (int i = 0; i < sz; ++i) {
|
||||||
|
QAndroidJniObject inputDevice = QAndroidJniObject::callStaticObjectMethod("android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;", buff[i]);
|
||||||
|
int sources = inputDevice.callMethod<jint>("getSources", "()I");
|
||||||
|
if (((sources & SOURCE_GAMEPAD) != SOURCE_GAMEPAD) //check if the input device is interesting to us
|
||||||
|
&& ((sources & SOURCE_JOYSTICK) != SOURCE_JOYSTICK)) continue;
|
||||||
|
|
||||||
|
// get id and name
|
||||||
|
QString id = inputDevice.callObjectMethod("getDescriptor", "()Ljava/lang/String;").toString();
|
||||||
|
QString name = inputDevice.callObjectMethod("getName", "()Ljava/lang/String;").toString();
|
||||||
|
|
||||||
|
names.push_back(name);
|
||||||
|
|
||||||
|
if (ret.contains(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get number of axis
|
||||||
|
QAndroidJniObject rangeListNative = inputDevice.callObjectMethod("getMotionRanges", "()Ljava/util/List;");
|
||||||
|
int axisCount = rangeListNative.callMethod<jint>("size");
|
||||||
|
|
||||||
|
// get number of buttons
|
||||||
|
jintArray a = env->NewIntArray(_androidBtnListCount);
|
||||||
|
env->SetIntArrayRegion(a,0,_androidBtnListCount,_androidBtnList);
|
||||||
|
QAndroidJniObject btns = inputDevice.callObjectMethod("hasKeys", "([I)[Z", a);
|
||||||
|
jbooleanArray jSupportedButtons = btns.object<jbooleanArray>();
|
||||||
|
jboolean* supportedButtons = env->GetBooleanArrayElements(jSupportedButtons, nullptr);
|
||||||
|
int buttonCount = 0;
|
||||||
|
for (int j=0;j<_androidBtnListCount;j++)
|
||||||
|
if (supportedButtons[j]) buttonCount++;
|
||||||
|
env->ReleaseBooleanArrayElements(jSupportedButtons, supportedButtons, 0);
|
||||||
|
|
||||||
|
qCDebug(JoystickLog) << "\t" << name << "id:" << buff[i] << "axes:" << axisCount << "buttons:" << buttonCount;
|
||||||
|
|
||||||
|
ret[name] = new JoystickAndroid(name, axisCount, buttonCount, buff[i], _multiVehicleManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = ret.begin(); i != ret.end();) {
|
||||||
|
if (!names.contains(i.key())) {
|
||||||
|
i = ret.erase(i);
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env->ReleaseIntArrayElements(jarr, buff, 0);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool JoystickAndroid::handleKeyEvent(jobject event) {
|
||||||
|
QJNIObjectPrivate ev(event);
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
const int _deviceId = ev.callMethod<jint>("getDeviceId", "()I");
|
||||||
|
if (_deviceId!=deviceId) return false;
|
||||||
|
|
||||||
|
const int action = ev.callMethod<jint>("getAction", "()I");
|
||||||
|
const int keyCode = ev.callMethod<jint>("getKeyCode", "()I");
|
||||||
|
|
||||||
|
for (int i = 0; i <_buttonCount; i++) {
|
||||||
|
if (btnCode[i] == keyCode) {
|
||||||
|
if (action == ACTION_DOWN) btnValue[i] = true;
|
||||||
|
if (action == ACTION_UP) btnValue[i] = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickAndroid::handleGenericMotionEvent(jobject event) {
|
||||||
|
QJNIObjectPrivate ev(event);
|
||||||
|
QMutexLocker lock(&m_mutex);
|
||||||
|
const int _deviceId = ev.callMethod<jint>("getDeviceId", "()I");
|
||||||
|
if (_deviceId!=deviceId) return false;
|
||||||
|
|
||||||
|
for (int i = 0; i <_axisCount; i++) {
|
||||||
|
const float v = ev.callMethod<jfloat>("getAxisValue", "(I)F",axisCode[i]);
|
||||||
|
axisValue[i] = static_cast<int>((v*32767.f));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickAndroid::_open(void) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickAndroid::_close(void) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickAndroid::_update(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickAndroid::_getButton(int i) {
|
||||||
|
return btnValue[ i ];
|
||||||
|
}
|
||||||
|
|
||||||
|
int JoystickAndroid::_getAxis(int i) {
|
||||||
|
return axisValue[ i ];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickAndroid::_getHat(int hat,int i) {
|
||||||
|
Q_UNUSED(hat);
|
||||||
|
Q_UNUSED(i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JoystickManager *_manager = nullptr;
|
||||||
|
|
||||||
|
//helper method
|
||||||
|
bool JoystickAndroid::init(JoystickManager *manager) {
|
||||||
|
_manager = manager;
|
||||||
|
|
||||||
|
//this gets list of all possible buttons - this is needed to check how many buttons our gamepad supports
|
||||||
|
//instead of the whole logic below we could have just a simple array of hardcoded int values as these 'should' not change
|
||||||
|
|
||||||
|
//int JoystickAndroid::_androidBtnListCount;
|
||||||
|
_androidBtnListCount = 31;
|
||||||
|
static int ret[31]; //there are 31 buttons in total accordingy to the API
|
||||||
|
int i;
|
||||||
|
//int *JoystickAndroid::
|
||||||
|
_androidBtnList = ret;
|
||||||
|
|
||||||
|
clear_jni_exception();
|
||||||
|
for (i = 1; i <= 16; i++) {
|
||||||
|
QString name = "KEYCODE_BUTTON_"+QString::number(i);
|
||||||
|
ret[i-1] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", name.toStdString().c_str());
|
||||||
|
}
|
||||||
|
i--;
|
||||||
|
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_A");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_B");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_C");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_L1");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_L2");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_R1");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_R2");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_MODE");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_SELECT");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_START");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_THUMBL");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_THUMBR");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_X");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_Y");
|
||||||
|
ret[i++] = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "KEYCODE_BUTTON_Z");
|
||||||
|
|
||||||
|
ACTION_DOWN = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "ACTION_DOWN");
|
||||||
|
ACTION_UP = QAndroidJniObject::getStaticField<jint>("android/view/KeyEvent", "ACTION_UP");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char kJniClassName[] {"org/mavlink/qgroundcontrol/QGCActivity"};
|
||||||
|
|
||||||
|
static void jniUpdateAvailableJoysticks(JNIEnv *envA, jobject thizA)
|
||||||
|
{
|
||||||
|
Q_UNUSED(envA);
|
||||||
|
Q_UNUSED(thizA);
|
||||||
|
|
||||||
|
if (_manager != nullptr) {
|
||||||
|
qCDebug(JoystickLog) << "jniUpdateAvailableJoysticks triggered";
|
||||||
|
emit _manager->updateAvailableJoysticksSignal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickAndroid::setNativeMethods()
|
||||||
|
{
|
||||||
|
qCDebug(JoystickLog) << "Registering Native Functions";
|
||||||
|
|
||||||
|
// REGISTER THE C++ FUNCTION WITH JNI
|
||||||
|
JNINativeMethod javaMethods[] {
|
||||||
|
{"nativeUpdateAvailableJoysticks", "()V", reinterpret_cast<void *>(jniUpdateAvailableJoysticks)}
|
||||||
|
};
|
||||||
|
|
||||||
|
clear_jni_exception();
|
||||||
|
QAndroidJniEnvironment jniEnv;
|
||||||
|
jclass objectClass = jniEnv->FindClass(kJniClassName);
|
||||||
|
if(!objectClass) {
|
||||||
|
clear_jni_exception();
|
||||||
|
qWarning() << "Couldn't find class:" << kJniClassName;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0]));
|
||||||
|
|
||||||
|
if (val < 0) {
|
||||||
|
qWarning() << "Error registering methods: " << val;
|
||||||
|
} else {
|
||||||
|
qCDebug(JoystickLog) << "Native Functions Registered";
|
||||||
|
}
|
||||||
|
clear_jni_exception();
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
#ifndef JOYSTICKANDROID_H
|
||||||
|
#define JOYSTICKANDROID_H
|
||||||
|
|
||||||
|
#include "Joystick.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
#include "MultiVehicleManager.h"
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include <QtCore/private/qjni_p.h>
|
||||||
|
#include <QtCore/private/qjnihelpers_p.h>
|
||||||
|
#include <QtAndroidExtras/QtAndroidExtras>
|
||||||
|
#include <QtAndroidExtras/QAndroidJniObject>
|
||||||
|
|
||||||
|
|
||||||
|
class JoystickAndroid : public Joystick, public QtAndroidPrivate::GenericMotionEventListener, public QtAndroidPrivate::KeyEventListener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JoystickAndroid(const QString& name, int axisCount, int buttonCount, int id, MultiVehicleManager* multiVehicleManager);
|
||||||
|
|
||||||
|
~JoystickAndroid();
|
||||||
|
|
||||||
|
static bool init(JoystickManager *manager);
|
||||||
|
|
||||||
|
static void setNativeMethods();
|
||||||
|
|
||||||
|
static QMap<QString, Joystick*> discover(MultiVehicleManager* _multiVehicleManager);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool handleKeyEvent(jobject event);
|
||||||
|
bool handleGenericMotionEvent(jobject event);
|
||||||
|
|
||||||
|
virtual bool _open ();
|
||||||
|
virtual void _close ();
|
||||||
|
virtual bool _update ();
|
||||||
|
|
||||||
|
virtual bool _getButton (int i);
|
||||||
|
virtual int _getAxis (int i);
|
||||||
|
virtual bool _getHat (int hat,int i);
|
||||||
|
|
||||||
|
int *btnCode;
|
||||||
|
int *axisCode;
|
||||||
|
bool *btnValue;
|
||||||
|
int *axisValue;
|
||||||
|
|
||||||
|
static int * _androidBtnList; //list of all possible android buttons
|
||||||
|
static int _androidBtnListCount;
|
||||||
|
|
||||||
|
static int ACTION_DOWN, ACTION_UP;
|
||||||
|
static QMutex m_mutex;
|
||||||
|
|
||||||
|
int deviceId;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // JOYSTICKANDROID_H
|
@ -0,0 +1,226 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
*
|
||||||
|
* QGroundControl is licensed according to the terms in the file
|
||||||
|
* COPYING.md in the root of the source code directory.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "JoystickManager.h"
|
||||||
|
#include "QGCApplication.h"
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#ifndef __mobile__
|
||||||
|
#include "JoystickSDL.h"
|
||||||
|
#define __sdljoystick__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __android__
|
||||||
|
#include "JoystickAndroid.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QGC_LOGGING_CATEGORY(JoystickManagerLog, "JoystickManagerLog")
|
||||||
|
|
||||||
|
const char * JoystickManager::_settingsGroup = "JoystickManager";
|
||||||
|
const char * JoystickManager::_settingsKeyActiveJoystick = "ActiveJoystick";
|
||||||
|
|
||||||
|
JoystickManager::JoystickManager(QGCApplication* app, QGCToolbox* toolbox)
|
||||||
|
: QGCTool(app, toolbox)
|
||||||
|
, _activeJoystick(nullptr)
|
||||||
|
, _multiVehicleManager(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
JoystickManager::~JoystickManager() {
|
||||||
|
QMap<QString, Joystick*>::iterator i;
|
||||||
|
for (i = _name2JoystickMap.begin(); i != _name2JoystickMap.end(); ++i) {
|
||||||
|
qCDebug(JoystickManagerLog) << "Releasing joystick:" << i.key();
|
||||||
|
i.value()->stop();
|
||||||
|
delete i.value();
|
||||||
|
}
|
||||||
|
qDebug() << "Done";
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickManager::setToolbox(QGCToolbox *toolbox)
|
||||||
|
{
|
||||||
|
QGCTool::setToolbox(toolbox);
|
||||||
|
|
||||||
|
_multiVehicleManager = _toolbox->multiVehicleManager();
|
||||||
|
|
||||||
|
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickManager::init() {
|
||||||
|
#ifdef __sdljoystick__
|
||||||
|
if (!JoystickSDL::init()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_setActiveJoystickFromSettings();
|
||||||
|
#elif defined(__android__)
|
||||||
|
if (!JoystickAndroid::init(this)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connect(this, &JoystickManager::updateAvailableJoysticksSignal, this, &JoystickManager::restartJoystickCheckTimer);
|
||||||
|
#endif
|
||||||
|
connect(&_joystickCheckTimer, &QTimer::timeout, this, &JoystickManager::_updateAvailableJoysticks);
|
||||||
|
_joystickCheckTimerCounter = 5;
|
||||||
|
_joystickCheckTimer.start(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickManager::_setActiveJoystickFromSettings(void)
|
||||||
|
{
|
||||||
|
QMap<QString,Joystick*> newMap;
|
||||||
|
|
||||||
|
#ifdef __sdljoystick__
|
||||||
|
// Get the latest joystick mapping
|
||||||
|
newMap = JoystickSDL::discover(_multiVehicleManager);
|
||||||
|
#elif defined(__android__)
|
||||||
|
newMap = JoystickAndroid::discover(_multiVehicleManager);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_activeJoystick && !newMap.contains(_activeJoystick->name())) {
|
||||||
|
qCDebug(JoystickManagerLog) << "Active joystick removed";
|
||||||
|
setActiveJoystick(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if our current mapping contains any joysticks that are not in the new mapping
|
||||||
|
// If so, those joysticks have been unplugged, and need to be cleaned up
|
||||||
|
QMap<QString, Joystick*>::iterator i;
|
||||||
|
for (i = _name2JoystickMap.begin(); i != _name2JoystickMap.end(); ++i) {
|
||||||
|
if (!newMap.contains(i.key())) {
|
||||||
|
qCDebug(JoystickManagerLog) << "Releasing joystick:" << i.key();
|
||||||
|
i.value()->stopPolling();
|
||||||
|
i.value()->wait(1000);
|
||||||
|
i.value()->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_name2JoystickMap = newMap;
|
||||||
|
emit availableJoysticksChanged();
|
||||||
|
|
||||||
|
if (!_name2JoystickMap.count()) {
|
||||||
|
setActiveJoystick(nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
|
||||||
|
settings.beginGroup(_settingsGroup);
|
||||||
|
QString name = settings.value(_settingsKeyActiveJoystick).toString();
|
||||||
|
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
name = _name2JoystickMap.first()->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveJoystick(_name2JoystickMap.value(name, _name2JoystickMap.first()));
|
||||||
|
settings.setValue(_settingsKeyActiveJoystick, _activeJoystick->name());
|
||||||
|
}
|
||||||
|
|
||||||
|
Joystick* JoystickManager::activeJoystick(void)
|
||||||
|
{
|
||||||
|
return _activeJoystick;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickManager::setActiveJoystick(Joystick* joystick)
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
|
||||||
|
if (joystick != nullptr && !_name2JoystickMap.contains(joystick->name())) {
|
||||||
|
qCWarning(JoystickManagerLog) << "Set active not in map" << joystick->name();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_activeJoystick == joystick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_activeJoystick) {
|
||||||
|
_activeJoystick->stopPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeJoystick = joystick;
|
||||||
|
|
||||||
|
if (_activeJoystick != nullptr) {
|
||||||
|
qCDebug(JoystickManagerLog) << "Set active:" << _activeJoystick->name();
|
||||||
|
|
||||||
|
settings.beginGroup(_settingsGroup);
|
||||||
|
settings.setValue(_settingsKeyActiveJoystick, _activeJoystick->name());
|
||||||
|
}
|
||||||
|
|
||||||
|
emit activeJoystickChanged(_activeJoystick);
|
||||||
|
emit activeJoystickNameChanged(_activeJoystick?_activeJoystick->name():"");
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList JoystickManager::joysticks(void)
|
||||||
|
{
|
||||||
|
QVariantList list;
|
||||||
|
|
||||||
|
for (const QString &name: _name2JoystickMap.keys()) {
|
||||||
|
list += QVariant::fromValue(_name2JoystickMap[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList JoystickManager::joystickNames(void)
|
||||||
|
{
|
||||||
|
return _name2JoystickMap.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString JoystickManager::activeJoystickName(void)
|
||||||
|
{
|
||||||
|
return _activeJoystick ? _activeJoystick->name() : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickManager::setActiveJoystickName(const QString& name)
|
||||||
|
{
|
||||||
|
if (_name2JoystickMap.contains(name)) {
|
||||||
|
setActiveJoystick(_name2JoystickMap[name]);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
qCWarning(JoystickManagerLog) << "Set active not in map" << name;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: move this to the right place: JoystickSDL.cc and JoystickAndroid.cc respectively and call through Joystick.cc
|
||||||
|
*/
|
||||||
|
void JoystickManager::_updateAvailableJoysticks()
|
||||||
|
{
|
||||||
|
#ifdef __sdljoystick__
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
switch(event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
qCDebug(JoystickManagerLog) << "SDL ERROR:" << SDL_GetError();
|
||||||
|
break;
|
||||||
|
case SDL_JOYDEVICEADDED:
|
||||||
|
qCDebug(JoystickManagerLog) << "Joystick added:" << event.jdevice.which;
|
||||||
|
_setActiveJoystickFromSettings();
|
||||||
|
break;
|
||||||
|
case SDL_JOYDEVICEREMOVED:
|
||||||
|
qCDebug(JoystickManagerLog) << "Joystick removed:" << event.jdevice.which;
|
||||||
|
_setActiveJoystickFromSettings();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif defined(__android__)
|
||||||
|
_joystickCheckTimerCounter--;
|
||||||
|
_setActiveJoystickFromSettings();
|
||||||
|
if (_joystickCheckTimerCounter <= 0) {
|
||||||
|
_joystickCheckTimer.stop();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickManager::restartJoystickCheckTimer()
|
||||||
|
{
|
||||||
|
_joystickCheckTimerCounter = 5;
|
||||||
|
_joystickCheckTimer.start(1000);
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
*
|
||||||
|
* QGroundControl is licensed according to the terms in the file
|
||||||
|
* COPYING.md in the root of the source code directory.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/// @file
|
||||||
|
/// @brief Joystick Manager
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "QGCLoggingCategory.h"
|
||||||
|
#include "Joystick.h"
|
||||||
|
#include "MultiVehicleManager.h"
|
||||||
|
#include "QGCToolbox.h"
|
||||||
|
|
||||||
|
#include <QVariantList>
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(JoystickManagerLog)
|
||||||
|
|
||||||
|
/// Joystick Manager
|
||||||
|
class JoystickManager : public QGCTool
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
JoystickManager(QGCApplication* app, QGCToolbox* toolbox);
|
||||||
|
~JoystickManager();
|
||||||
|
|
||||||
|
Q_PROPERTY(QVariantList joysticks READ joysticks NOTIFY availableJoysticksChanged)
|
||||||
|
Q_PROPERTY(QStringList joystickNames READ joystickNames NOTIFY availableJoysticksChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(Joystick* activeJoystick READ activeJoystick WRITE setActiveJoystick NOTIFY activeJoystickChanged)
|
||||||
|
Q_PROPERTY(QString activeJoystickName READ activeJoystickName WRITE setActiveJoystickName NOTIFY activeJoystickNameChanged)
|
||||||
|
|
||||||
|
/// List of available joysticks
|
||||||
|
QVariantList joysticks();
|
||||||
|
/// List of available joystick names
|
||||||
|
QStringList joystickNames(void);
|
||||||
|
|
||||||
|
/// Get active joystick
|
||||||
|
Joystick* activeJoystick(void);
|
||||||
|
/// Set active joystick
|
||||||
|
void setActiveJoystick(Joystick* joystick);
|
||||||
|
|
||||||
|
QString activeJoystickName(void);
|
||||||
|
bool setActiveJoystickName(const QString& name);
|
||||||
|
|
||||||
|
void restartJoystickCheckTimer(void);
|
||||||
|
|
||||||
|
// Override from QGCTool
|
||||||
|
virtual void setToolbox(QGCToolbox *toolbox);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void init();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void activeJoystickChanged(Joystick* joystick);
|
||||||
|
void activeJoystickNameChanged(const QString& name);
|
||||||
|
void availableJoysticksChanged(void);
|
||||||
|
void updateAvailableJoysticksSignal();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void _updateAvailableJoysticks(void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void _setActiveJoystickFromSettings(void);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Joystick* _activeJoystick;
|
||||||
|
QMap<QString, Joystick*> _name2JoystickMap;
|
||||||
|
MultiVehicleManager* _multiVehicleManager;
|
||||||
|
|
||||||
|
static const char * _settingsGroup;
|
||||||
|
static const char * _settingsKeyActiveJoystick;
|
||||||
|
|
||||||
|
int _joystickCheckTimerCounter;
|
||||||
|
QTimer _joystickCheckTimer;
|
||||||
|
};
|
@ -0,0 +1,101 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
*
|
||||||
|
* QGroundControl is licensed according to the terms in the file
|
||||||
|
* COPYING.md in the root of the source code directory.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "JoystickMavCommand.h"
|
||||||
|
#include "QGCLoggingCategory.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonParseError>
|
||||||
|
#include <QJsonArray>
|
||||||
|
|
||||||
|
QGC_LOGGING_CATEGORY(JoystickMavCommandLog, "JoystickMavCommandLog")
|
||||||
|
|
||||||
|
static void parseJsonValue(const QJsonObject& jsonObject, const QString& key, float& param)
|
||||||
|
{
|
||||||
|
if (jsonObject.contains(key))
|
||||||
|
param = static_cast<float>(jsonObject.value(key).toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<JoystickMavCommand> JoystickMavCommand::load(const QString& jsonFilename)
|
||||||
|
{
|
||||||
|
qCDebug(JoystickMavCommandLog) << "Loading" << jsonFilename;
|
||||||
|
QList<JoystickMavCommand> result;
|
||||||
|
|
||||||
|
QFile jsonFile(jsonFilename);
|
||||||
|
if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
qCDebug(JoystickMavCommandLog) << "Could not open" << jsonFilename;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray bytes = jsonFile.readAll();
|
||||||
|
jsonFile.close();
|
||||||
|
QJsonParseError jsonParseError;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(bytes, &jsonParseError);
|
||||||
|
if (jsonParseError.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << jsonFilename << "Unable to open json document" << jsonParseError.errorString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json = doc.object();
|
||||||
|
|
||||||
|
const int version = json.value("version").toInt();
|
||||||
|
if (version != 1) {
|
||||||
|
qWarning() << jsonFilename << ": invalid version" << version;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonValue jsonValue = json.value("commands");
|
||||||
|
if (!jsonValue.isArray()) {
|
||||||
|
qWarning() << jsonFilename << ": 'commands' is not an array";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray jsonArray = jsonValue.toArray();
|
||||||
|
for (QJsonValue info: jsonArray) {
|
||||||
|
if (!info.isObject()) {
|
||||||
|
qWarning() << jsonFilename << ": 'commands' should contain objects";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto jsonObject = info.toObject();
|
||||||
|
JoystickMavCommand item;
|
||||||
|
if (!jsonObject.contains("id")) {
|
||||||
|
qWarning() << jsonFilename << ": 'id' is required";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
item._id = jsonObject.value("id").toInt();
|
||||||
|
if (!jsonObject.contains("name")) {
|
||||||
|
qWarning() << jsonFilename << ": 'name' is required";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
item._name = jsonObject.value("name").toString();
|
||||||
|
item._showError = jsonObject.value("showError").toBool();
|
||||||
|
parseJsonValue(jsonObject, "param1", item._param1);
|
||||||
|
parseJsonValue(jsonObject, "param2", item._param2);
|
||||||
|
parseJsonValue(jsonObject, "param3", item._param3);
|
||||||
|
parseJsonValue(jsonObject, "param4", item._param4);
|
||||||
|
parseJsonValue(jsonObject, "param5", item._param5);
|
||||||
|
parseJsonValue(jsonObject, "param6", item._param6);
|
||||||
|
parseJsonValue(jsonObject, "param7", item._param7);
|
||||||
|
|
||||||
|
qCDebug(JoystickMavCommandLog) << jsonObject;
|
||||||
|
|
||||||
|
result.append(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickMavCommand::send(Vehicle* vehicle)
|
||||||
|
{
|
||||||
|
vehicle->sendMavCommand(vehicle->defaultComponentId(),
|
||||||
|
static_cast<MAV_CMD>(_id),
|
||||||
|
_showError,
|
||||||
|
_param1, _param2, _param3, _param4, _param5, _param6, _param7);
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
*
|
||||||
|
* QGroundControl is licensed according to the terms in the file
|
||||||
|
* COPYING.md in the root of the source code directory.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/// @file
|
||||||
|
/// @brief Custom Joystick MAV command
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
class Vehicle;
|
||||||
|
|
||||||
|
/// Custom MAV command
|
||||||
|
class JoystickMavCommand
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static QList<JoystickMavCommand> load(const QString& jsonFilename);
|
||||||
|
QString name() const { return _name; }
|
||||||
|
|
||||||
|
void send(Vehicle* vehicle);
|
||||||
|
private:
|
||||||
|
QString _name;
|
||||||
|
int _id = 0;
|
||||||
|
bool _showError = false;
|
||||||
|
float _param1 = 0.0f;
|
||||||
|
float _param2 = 0.0f;
|
||||||
|
float _param3 = 0.0f;
|
||||||
|
float _param4 = 0.0f;
|
||||||
|
float _param5 = 0.0f;
|
||||||
|
float _param6 = 0.0f;
|
||||||
|
float _param7 = 0.0f;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"comment": "Joystick MAV commands",
|
||||||
|
"version": 1,
|
||||||
|
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"id": 31010,
|
||||||
|
"name": "MAV_CMD_USER_1",
|
||||||
|
"param1": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31011,
|
||||||
|
"name": "MAV_CMD_USER_2",
|
||||||
|
"param1": 0.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,178 @@
|
|||||||
|
#include "JoystickSDL.h"
|
||||||
|
|
||||||
|
#include "QGCApplication.h"
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
JoystickSDL::JoystickSDL(const QString& name, int axisCount, int buttonCount, int hatCount, int index, bool isGameController, MultiVehicleManager* multiVehicleManager)
|
||||||
|
: Joystick(name,axisCount,buttonCount,hatCount,multiVehicleManager)
|
||||||
|
, _isGameController(isGameController)
|
||||||
|
, _index(index)
|
||||||
|
{
|
||||||
|
if(_isGameController) _setDefaultCalibration();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickSDL::init(void) {
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK) < 0) {
|
||||||
|
SDL_JoystickEventState(SDL_ENABLE);
|
||||||
|
qWarning() << "Couldn't initialize SimpleDirectMediaLayer:" << SDL_GetError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_loadGameControllerMappings();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QString, Joystick*> JoystickSDL::discover(MultiVehicleManager* _multiVehicleManager) {
|
||||||
|
static QMap<QString, Joystick*> ret;
|
||||||
|
|
||||||
|
QMap<QString,Joystick*> newRet;
|
||||||
|
|
||||||
|
// Load available joysticks
|
||||||
|
|
||||||
|
qCDebug(JoystickLog) << "Available joysticks";
|
||||||
|
|
||||||
|
for (int i=0; i<SDL_NumJoysticks(); i++) {
|
||||||
|
QString name = SDL_JoystickNameForIndex(i);
|
||||||
|
|
||||||
|
if (!ret.contains(name)) {
|
||||||
|
int axisCount, buttonCount, hatCount;
|
||||||
|
bool isGameController = SDL_IsGameController(i);
|
||||||
|
|
||||||
|
if (SDL_Joystick* sdlJoystick = SDL_JoystickOpen(i)) {
|
||||||
|
SDL_ClearError();
|
||||||
|
axisCount = SDL_JoystickNumAxes(sdlJoystick);
|
||||||
|
buttonCount = SDL_JoystickNumButtons(sdlJoystick);
|
||||||
|
hatCount = SDL_JoystickNumHats(sdlJoystick);
|
||||||
|
if (axisCount < 0 || buttonCount < 0 || hatCount < 0) {
|
||||||
|
qCWarning(JoystickLog) << "\t libsdl error parsing joystick features:" << SDL_GetError();
|
||||||
|
}
|
||||||
|
SDL_JoystickClose(sdlJoystick);
|
||||||
|
} else {
|
||||||
|
qCWarning(JoystickLog) << "\t libsdl failed opening joystick" << qPrintable(name) << "error:" << SDL_GetError();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(JoystickLog) << "\t" << name << "axes:" << axisCount << "buttons:" << buttonCount << "hats:" << hatCount << "isGC:" << isGameController;
|
||||||
|
|
||||||
|
// Check for joysticks with duplicate names and differentiate the keys when necessary.
|
||||||
|
// This is required when using an Xbox 360 wireless receiver that always identifies as
|
||||||
|
// 4 individual joysticks, regardless of how many joysticks are actually connected to the
|
||||||
|
// receiver. Using GUID does not help, all of these devices present the same GUID.
|
||||||
|
QString originalName = name;
|
||||||
|
uint8_t duplicateIdx = 1;
|
||||||
|
while (newRet[name]) {
|
||||||
|
name = QString("%1 %2").arg(originalName).arg(duplicateIdx++);
|
||||||
|
}
|
||||||
|
|
||||||
|
newRet[name] = new JoystickSDL(name, qMax(0,axisCount), qMax(0,buttonCount), qMax(0,hatCount), i, isGameController, _multiVehicleManager);
|
||||||
|
} else {
|
||||||
|
newRet[name] = ret[name];
|
||||||
|
JoystickSDL *j = static_cast<JoystickSDL*>(newRet[name]);
|
||||||
|
if (j->index() != i) {
|
||||||
|
j->setIndex(i); // This joystick index has been remapped by SDL
|
||||||
|
}
|
||||||
|
// Anything left in ret after we exit the loop has been removed (unplugged) and needs to be cleaned up.
|
||||||
|
// We will handle that in JoystickManager in case the removed joystick was in use.
|
||||||
|
ret.remove(name);
|
||||||
|
qCDebug(JoystickLog) << "\tSkipping duplicate" << name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newRet.count()) {
|
||||||
|
qCDebug(JoystickLog) << "\tnone found";
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = newRet;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickSDL::_loadGameControllerMappings(void) {
|
||||||
|
QFile file(":/db/mapping/joystick/gamecontrollerdb.txt");
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
|
{
|
||||||
|
qWarning() << "Couldn't load GameController mapping database.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream s(&file);
|
||||||
|
|
||||||
|
while (!s.atEnd()) {
|
||||||
|
SDL_GameControllerAddMapping(s.readLine().toStdString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickSDL::_open(void) {
|
||||||
|
if ( _isGameController ) {
|
||||||
|
sdlController = SDL_GameControllerOpen(_index);
|
||||||
|
sdlJoystick = SDL_GameControllerGetJoystick(sdlController);
|
||||||
|
} else {
|
||||||
|
sdlJoystick = SDL_JoystickOpen(_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sdlJoystick) {
|
||||||
|
qCWarning(JoystickLog) << "SDL_JoystickOpen failed:" << SDL_GetError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(JoystickLog) << "Opened joystick at" << sdlJoystick;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickSDL::_close(void) {
|
||||||
|
if (sdlJoystick == nullptr) {
|
||||||
|
qCDebug(JoystickLog) << "Attempt to close null joystick!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(JoystickLog) << "Closing" << SDL_JoystickName(sdlJoystick) << "at" << sdlJoystick;
|
||||||
|
|
||||||
|
// We get a segfault if we try to close a joystick that has been detached
|
||||||
|
if (SDL_JoystickGetAttached(sdlJoystick) == SDL_FALSE) {
|
||||||
|
qCDebug(JoystickLog) << "\tJoystick is not attached!";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (SDL_JoystickInstanceID(sdlJoystick) != -1) {
|
||||||
|
qCDebug(JoystickLog) << "\tID:" << SDL_JoystickInstanceID(sdlJoystick);
|
||||||
|
// This segfaults so often, and I've spent so much time trying to find the cause and fix it
|
||||||
|
// I think this might be an SDL bug
|
||||||
|
// We are much more stable just commenting this out
|
||||||
|
//SDL_JoystickClose(sdlJoystick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sdlJoystick = nullptr;
|
||||||
|
sdlController = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickSDL::_update(void)
|
||||||
|
{
|
||||||
|
SDL_JoystickUpdate();
|
||||||
|
SDL_GameControllerUpdate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickSDL::_getButton(int i) {
|
||||||
|
if (_isGameController) {
|
||||||
|
return SDL_GameControllerGetButton(sdlController, SDL_GameControllerButton(i)) == 1;
|
||||||
|
} else {
|
||||||
|
return SDL_JoystickGetButton(sdlJoystick, i) == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int JoystickSDL::_getAxis(int i) {
|
||||||
|
if (_isGameController) {
|
||||||
|
return SDL_GameControllerGetAxis(sdlController, SDL_GameControllerAxis(i));
|
||||||
|
} else {
|
||||||
|
return SDL_JoystickGetAxis(sdlJoystick, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JoystickSDL::_getHat(int hat, int i) {
|
||||||
|
uint8_t hatButtons[] = {SDL_HAT_UP,SDL_HAT_DOWN,SDL_HAT_LEFT,SDL_HAT_RIGHT};
|
||||||
|
if (i < int(sizeof(hatButtons))) {
|
||||||
|
return (SDL_JoystickGetHat(sdlJoystick, hat) & hatButtons[i]) != 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
*
|
||||||
|
* QGroundControl is licensed according to the terms in the file
|
||||||
|
* COPYING.md in the root of the source code directory.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/// @file
|
||||||
|
/// @brief SDL Joystick Interface
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Joystick.h"
|
||||||
|
#include "Vehicle.h"
|
||||||
|
#include "MultiVehicleManager.h"
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
/// @brief SDL Joystick Interface
|
||||||
|
class JoystickSDL : public Joystick
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JoystickSDL(const QString& name, int axisCount, int buttonCount, int hatCount, int index, bool isGameController, MultiVehicleManager* multiVehicleManager);
|
||||||
|
|
||||||
|
static QMap<QString, Joystick*> discover(MultiVehicleManager* _multiVehicleManager);
|
||||||
|
static bool init(void);
|
||||||
|
|
||||||
|
int index(void) const { return _index; }
|
||||||
|
void setIndex(int index) { _index = index; }
|
||||||
|
|
||||||
|
// This can be uncommented to hide the calibration buttons for gamecontrollers in the future
|
||||||
|
// bool requiresCalibration(void) final { return !_isGameController; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void _loadGameControllerMappings();
|
||||||
|
|
||||||
|
bool _open () final;
|
||||||
|
void _close () final;
|
||||||
|
bool _update () final;
|
||||||
|
|
||||||
|
bool _getButton (int i) final;
|
||||||
|
int _getAxis (int i) final;
|
||||||
|
bool _getHat (int hat,int i) final;
|
||||||
|
|
||||||
|
SDL_Joystick* sdlJoystick;
|
||||||
|
SDL_GameController* sdlController;
|
||||||
|
|
||||||
|
bool _isGameController;
|
||||||
|
int _index; ///< Index for SDL_JoystickOpen
|
||||||
|
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
add_library(PositionManager
|
||||||
|
PositionManager.cpp
|
||||||
|
SimulatedPosition.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(PositionManager
|
||||||
|
PUBLIC
|
||||||
|
qgc
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(PositionManager
|
||||||
|
PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,202 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
*
|
||||||
|
* QGroundControl is licensed according to the terms in the file
|
||||||
|
* COPYING.md in the root of the source code directory.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "PositionManager.h"
|
||||||
|
#include "QGCApplication.h"
|
||||||
|
#include "QGCCorePlugin.h"
|
||||||
|
|
||||||
|
#if !defined(NO_SERIAL_LINK) && !defined(__android__)
|
||||||
|
#include <QSerialPortInfo>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QtPositioning/private/qgeopositioninfosource_p.h>
|
||||||
|
|
||||||
|
QGCPositionManager::QGCPositionManager(QGCApplication* app, QGCToolbox* toolbox)
|
||||||
|
: QGCTool (app, toolbox)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QGCPositionManager::~QGCPositionManager()
|
||||||
|
{
|
||||||
|
delete(_simulatedSource);
|
||||||
|
delete(_nmeaSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QGCPositionManager::setToolbox(QGCToolbox *toolbox)
|
||||||
|
{
|
||||||
|
QGCTool::setToolbox(toolbox);
|
||||||
|
//-- First see if plugin provides a position source
|
||||||
|
_defaultSource = toolbox->corePlugin()->createPositionSource(this);
|
||||||
|
if (_defaultSource) {
|
||||||
|
_usingPluginSource = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qgcApp()->runningUnitTests()) {
|
||||||
|
// Units test on travis fail due to lack of position source
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_defaultSource) {
|
||||||
|
//-- Otherwise, create a default one
|
||||||
|
#if 1
|
||||||
|
// Calling this can end up falling through a path which tries to instantiate a serialnmea source.
|
||||||
|
// The Qt code for this will pop a qWarning if there are no serial ports available. This in turn
|
||||||
|
// causes you to be unable to run with QT_FATAL_WARNINGS=1 to debug stuff.
|
||||||
|
_defaultSource = QGeoPositionInfoSource::createDefaultSource(this);
|
||||||
|
#else
|
||||||
|
// So instead we create our own version of QGeoPositionInfoSource::createDefaultSource which isn't as stupid.
|
||||||
|
QList<QCborMap> plugins = QGeoPositionInfoSourcePrivate::pluginsSorted();
|
||||||
|
foreach (const auto &obj, plugins) {
|
||||||
|
if (obj.value("Position").isBool() && obj.value("Position").toBool()) {
|
||||||
|
QString pluginName = obj.value("Keys").toArray()[0].toString();
|
||||||
|
if (pluginName == "serialnmea") {
|
||||||
|
#if !defined(NO_SERIAL_LINK) && !defined(__android__)
|
||||||
|
if (QSerialPortInfo::availablePorts().isEmpty()) {
|
||||||
|
// This prevents the qWarning from popping
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
continue;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
_defaultSource = QGeoPositionInfoSource::createSource(pluginName, this);
|
||||||
|
if (_defaultSource) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
_simulatedSource = new SimulatedPosition();
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
setPositionSource(QGCPositionSource::InternalGPS);
|
||||||
|
#else
|
||||||
|
setPositionSource(QGCPositionManager::Simulated);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void QGCPositionManager::setNmeaSourceDevice(QIODevice* device)
|
||||||
|
{
|
||||||
|
// stop and release _nmeaSource
|
||||||
|
if (_nmeaSource) {
|
||||||
|
_nmeaSource->stopUpdates();
|
||||||
|
disconnect(_nmeaSource);
|
||||||
|
|
||||||
|
// if _currentSource is pointing there, point to null
|
||||||
|
if (_currentSource == _nmeaSource){
|
||||||
|
_currentSource = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete _nmeaSource;
|
||||||
|
_nmeaSource = nullptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
_nmeaSource = new QNmeaPositionInfoSource(QNmeaPositionInfoSource::RealTimeMode, this);
|
||||||
|
_nmeaSource->setDevice(device);
|
||||||
|
// set equivalent range error to enable position accuracy reporting
|
||||||
|
_nmeaSource->setUserEquivalentRangeError(5.1);
|
||||||
|
setPositionSource(QGCPositionManager::NmeaGPS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QGCPositionManager::_positionUpdated(const QGeoPositionInfo &update)
|
||||||
|
{
|
||||||
|
_geoPositionInfo = update;
|
||||||
|
|
||||||
|
QGeoCoordinate newGCSPosition = QGeoCoordinate();
|
||||||
|
qreal newGCSHeading = update.attribute(QGeoPositionInfo::Direction);
|
||||||
|
|
||||||
|
if (update.isValid() && update.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) {
|
||||||
|
// Note that gcsPosition filters out possible crap values
|
||||||
|
if (qAbs(update.coordinate().latitude()) > 0.001 &&
|
||||||
|
qAbs(update.coordinate().longitude()) > 0.001 ) {
|
||||||
|
_gcsPositionHorizontalAccuracy = update.attribute(QGeoPositionInfo::HorizontalAccuracy);
|
||||||
|
if (_gcsPositionHorizontalAccuracy <= MinHorizonalAccuracyMeters) {
|
||||||
|
newGCSPosition = update.coordinate();
|
||||||
|
}
|
||||||
|
emit gcsPositionHorizontalAccuracyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newGCSPosition != _gcsPosition) {
|
||||||
|
_gcsPosition = newGCSPosition;
|
||||||
|
emit gcsPositionChanged(_gcsPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point only plugins support gcs heading. The reason is that the quality of heading information from a local
|
||||||
|
// position device (not a compass) is unknown. In many cases it can only be trusted if the GCS location is moving above
|
||||||
|
// a certain rate of speed. When it is not, or the gcs is standing still the heading is just random. We don't want these
|
||||||
|
// random heading to be shown on the fly view. So until we can get a true compass based heading or some smarted heading quality
|
||||||
|
// information which takes into account the speed of movement we normally don't set a heading. We do use the heading though
|
||||||
|
// if the plugin overrides the position source. In that case we assume that it hopefully know what it is doing.
|
||||||
|
if (_usingPluginSource && newGCSHeading != _gcsHeading) {
|
||||||
|
_gcsHeading = newGCSHeading;
|
||||||
|
emit gcsHeadingChanged(_gcsHeading);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit positionInfoUpdated(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
int QGCPositionManager::updateInterval() const
|
||||||
|
{
|
||||||
|
return _updateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QGCPositionManager::setPositionSource(QGCPositionManager::QGCPositionSource source)
|
||||||
|
{
|
||||||
|
if (_currentSource != nullptr) {
|
||||||
|
_currentSource->stopUpdates();
|
||||||
|
disconnect(_currentSource);
|
||||||
|
|
||||||
|
// Reset all values so we dont get stuck on old values
|
||||||
|
_geoPositionInfo = QGeoPositionInfo();
|
||||||
|
_gcsPosition = QGeoCoordinate();
|
||||||
|
_gcsHeading = qQNaN();
|
||||||
|
_gcsPositionHorizontalAccuracy = std::numeric_limits<qreal>::infinity();
|
||||||
|
|
||||||
|
emit gcsPositionChanged(_gcsPosition);
|
||||||
|
emit gcsHeadingChanged(_gcsHeading);
|
||||||
|
emit positionInfoUpdated(_geoPositionInfo);
|
||||||
|
emit gcsPositionHorizontalAccuracyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qgcApp()->runningUnitTests()) {
|
||||||
|
// Units test on travis fail due to lack of position source
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(source) {
|
||||||
|
case QGCPositionManager::Log:
|
||||||
|
break;
|
||||||
|
case QGCPositionManager::Simulated:
|
||||||
|
_currentSource = _simulatedSource;
|
||||||
|
break;
|
||||||
|
case QGCPositionManager::NmeaGPS:
|
||||||
|
_currentSource = _nmeaSource;
|
||||||
|
break;
|
||||||
|
case QGCPositionManager::InternalGPS:
|
||||||
|
default:
|
||||||
|
_currentSource = _defaultSource;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentSource != nullptr) {
|
||||||
|
_updateInterval = _currentSource->minimumUpdateInterval();
|
||||||
|
_currentSource->setPreferredPositioningMethods(QGeoPositionInfoSource::SatellitePositioningMethods);
|
||||||
|
_currentSource->setUpdateInterval(_updateInterval);
|
||||||
|
connect(_currentSource, &QGeoPositionInfoSource::positionUpdated, this, &QGCPositionManager::_positionUpdated);
|
||||||
|
connect(_currentSource, &QGeoPositionInfoSource::errorOccurred, this, &QGCPositionManager::_error);
|
||||||
|
_currentSource->startUpdates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QGCPositionManager::_error(QGeoPositionInfoSource::Error positioningError)
|
||||||
|
{
|
||||||
|
qWarning() << "QGCPositionManager error" << positioningError;
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
*
|
||||||
|
* QGroundControl is licensed according to the terms in the file
|
||||||
|
* COPYING.md in the root of the source code directory.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <QtCore>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDate>
|
||||||
|
|
||||||
|
#include "SimulatedPosition.h"
|
||||||
|
#include "QGCApplication.h"
|
||||||
|
#include "MultiVehicleManager.h"
|
||||||
|
|
||||||
|
SimulatedPosition::SimulatedPosition()
|
||||||
|
: QGeoPositionInfoSource(nullptr)
|
||||||
|
{
|
||||||
|
_updateTimer.setSingleShot(false);
|
||||||
|
|
||||||
|
// Initialize position to normal PX4 Gazebo home position
|
||||||
|
_lastPosition.setTimestamp(QDateTime::currentDateTime());
|
||||||
|
_lastPosition.setCoordinate(QGeoCoordinate(47.3977420, 8.5455941, 488));
|
||||||
|
_lastPosition.setAttribute(QGeoPositionInfo::Attribute::Direction, _heading);
|
||||||
|
_lastPosition.setAttribute(QGeoPositionInfo::Attribute::GroundSpeed, _horizontalVelocityMetersPerSec);
|
||||||
|
_lastPosition.setAttribute(QGeoPositionInfo::Attribute::VerticalSpeed, _verticalVelocityMetersPerSec);
|
||||||
|
|
||||||
|
// When a vehicle shows up we switch location to the vehicle home position
|
||||||
|
connect(qgcApp()->toolbox()->multiVehicleManager(), &MultiVehicleManager::vehicleAdded, this, &SimulatedPosition::_vehicleAdded);
|
||||||
|
|
||||||
|
connect(&_updateTimer, &QTimer::timeout, this, &SimulatedPosition::_updatePosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
QGeoPositionInfo SimulatedPosition::lastKnownPosition(bool /*fromSatellitePositioningMethodsOnly*/) const
|
||||||
|
{
|
||||||
|
return _lastPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
SimulatedPosition::PositioningMethods SimulatedPosition::supportedPositioningMethods() const
|
||||||
|
{
|
||||||
|
return AllPositioningMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedPosition::startUpdates(void)
|
||||||
|
{
|
||||||
|
_updateTimer.start(qMax(updateInterval(), minimumUpdateInterval()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedPosition::stopUpdates(void)
|
||||||
|
{
|
||||||
|
_updateTimer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedPosition::requestUpdate(int /*timeout*/)
|
||||||
|
{
|
||||||
|
emit errorOccurred(QGeoPositionInfoSource::UpdateTimeoutError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedPosition::_updatePosition(void)
|
||||||
|
{
|
||||||
|
int intervalMsecs = _updateTimer.interval();
|
||||||
|
|
||||||
|
QGeoCoordinate coord = _lastPosition.coordinate();
|
||||||
|
double horizontalDistance = _horizontalVelocityMetersPerSec * (1000.0 / static_cast<double>(intervalMsecs));
|
||||||
|
double verticalDistance = _verticalVelocityMetersPerSec * (1000.0 / static_cast<double>(intervalMsecs));
|
||||||
|
|
||||||
|
_lastPosition.setCoordinate(coord.atDistanceAndAzimuth(horizontalDistance, _heading, verticalDistance));
|
||||||
|
|
||||||
|
emit positionUpdated(_lastPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
QGeoPositionInfoSource::Error SimulatedPosition::error() const
|
||||||
|
{
|
||||||
|
return QGeoPositionInfoSource::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedPosition::_vehicleAdded(Vehicle* vehicle)
|
||||||
|
{
|
||||||
|
if (vehicle->homePosition().isValid()) {
|
||||||
|
_lastPosition.setCoordinate(vehicle->homePosition());
|
||||||
|
} else {
|
||||||
|
connect(vehicle, &Vehicle::homePositionChanged, this, &SimulatedPosition::_vehicleHomePositionChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimulatedPosition::_vehicleHomePositionChanged(QGeoCoordinate homePosition)
|
||||||
|
{
|
||||||
|
Vehicle* vehicle = qobject_cast<Vehicle*>(sender());
|
||||||
|
|
||||||
|
if (homePosition.isValid()) {
|
||||||
|
_lastPosition.setCoordinate(homePosition);
|
||||||
|
disconnect(vehicle, &Vehicle::homePositionChanged, this, &SimulatedPosition::_vehicleHomePositionChanged);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||||||
|
*
|
||||||
|
* QGroundControl is licensed according to the terms in the file
|
||||||
|
* COPYING.md in the root of the source code directory.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtPositioning/qgeopositioninfosource.h>
|
||||||
|
#include "QGCToolbox.h"
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
class Vehicle;
|
||||||
|
|
||||||
|
class SimulatedPosition : public QGeoPositionInfoSource
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
SimulatedPosition();
|
||||||
|
|
||||||
|
QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const override;
|
||||||
|
|
||||||
|
PositioningMethods supportedPositioningMethods (void) const override;
|
||||||
|
int minimumUpdateInterval (void) const override { return _updateIntervalMsecs; }
|
||||||
|
Error error (void) const override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void startUpdates (void) override;
|
||||||
|
void stopUpdates (void) override;
|
||||||
|
void requestUpdate (int timeout = 5000) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void _updatePosition (void);
|
||||||
|
void _vehicleAdded (Vehicle* vehicle);
|
||||||
|
void _vehicleHomePositionChanged (QGeoCoordinate homePosition);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTimer _updateTimer;
|
||||||
|
QGeoPositionInfo _lastPosition;
|
||||||
|
|
||||||
|
static constexpr int _updateIntervalMsecs = 1000;
|
||||||
|
static constexpr double _horizontalVelocityMetersPerSec = 0.5;
|
||||||
|
static constexpr double _verticalVelocityMetersPerSec = 0.1;
|
||||||
|
static constexpr double _heading = 45;
|
||||||
|
};
|
Loading…
Reference in new issue