laptoy 6 months ago
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,74 @@
/****************************************************************************
*
* (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 <QGeoPositionInfoSource>
#include <QNmeaPositionInfoSource>
#include <QVariant>
#include "QGCToolbox.h"
#include "SimulatedPosition.h"
class QGCPositionManager : public QGCTool {
Q_OBJECT
public:
static constexpr size_t MinHorizonalAccuracyMeters = 100;
QGCPositionManager(QGCApplication* app, QGCToolbox* toolbox);
~QGCPositionManager();
Q_PROPERTY(QGeoCoordinate gcsPosition READ gcsPosition NOTIFY gcsPositionChanged)
Q_PROPERTY(qreal gcsHeading READ gcsHeading NOTIFY gcsHeadingChanged)
Q_PROPERTY(qreal gcsPositionHorizontalAccuracy READ gcsPositionHorizontalAccuracy
NOTIFY gcsPositionHorizontalAccuracyChanged)
enum QGCPositionSource {
Simulated,
InternalGPS,
Log,
NmeaGPS
};
QGeoCoordinate gcsPosition (void) { return _gcsPosition; }
qreal gcsHeading (void) const{ return _gcsHeading; }
qreal gcsPositionHorizontalAccuracy(void) const { return _gcsPositionHorizontalAccuracy; }
QGeoPositionInfo geoPositionInfo (void) const { return _geoPositionInfo; }
void setPositionSource (QGCPositionSource source);
int updateInterval (void) const;
void setNmeaSourceDevice (QIODevice* device);
// Overrides from QGCTool
void setToolbox(QGCToolbox* toolbox) override;
private slots:
void _positionUpdated(const QGeoPositionInfo &update);
void _error(QGeoPositionInfoSource::Error positioningError);
signals:
void gcsPositionChanged(QGeoCoordinate gcsPosition);
void gcsHeadingChanged(qreal gcsHeading);
void positionInfoUpdated(QGeoPositionInfo update);
void gcsPositionHorizontalAccuracyChanged();
private:
int _updateInterval = 0;
QGeoPositionInfo _geoPositionInfo;
QGeoCoordinate _gcsPosition;
qreal _gcsHeading = qQNaN();
qreal _gcsPositionHorizontalAccuracy = std::numeric_limits<qreal>::infinity();
QGeoPositionInfoSource* _currentSource = nullptr;
QGeoPositionInfoSource* _defaultSource = nullptr;
QNmeaPositionInfoSource* _nmeaSource = nullptr;
QGeoPositionInfoSource* _simulatedSource = nullptr;
bool _usingPluginSource = false;
};

@ -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…
Cancel
Save