QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
Joystick.cc
Go to the documentation of this file.
1#include "Joystick.h"
2#include "Fact.h"
3#include "MavlinkAction.h"
6#include "FirmwarePlugin.h"
7#include "GimbalController.h"
8#include "QGCCorePlugin.h"
10#include "QmlObjectListModel.h"
11#include "SettingsManager.h"
12#include "Vehicle.h"
13#include "VehicleSupports.h"
14#include "JoystickManager.h"
15#include "MultiVehicleManager.h"
16
17#include <QtCore/QCoreApplication>
18#include <QtCore/QSet>
19#include <algorithm>
20#include <cmath>
21#include <QtCore/QSettings>
22#include <QtCore/QThread>
23
24QGC_LOGGING_CATEGORY(JoystickLog, "Joystick.Joystick")
25QGC_LOGGING_CATEGORY(JoystickVerboseLog, "Joystick.Joystick:verbose")
26
27static QDebug operator<<(QDebug debug, Joystick::ButtonEvent_t event)
28{
29 switch (event) {
30 case Joystick::ButtonEventDownTransition: return debug << "Down";
31 case Joystick::ButtonEventUpTransition: return debug << "Up";
32 case Joystick::ButtonEventRepeat: return debug << "Repeat";
33 case Joystick::ButtonEventNone: return debug << "None";
34 }
35 return debug << static_cast<int>(event);
36}
37
38namespace
39{
40static constexpr const char* kButtonActionArrayGroup = "JoystickButtonActionSettingsArray";
41static constexpr const char* kButtonActionNameKey = "actionName";
42static constexpr const char* kButtonRepeatKey = "repeat";
43static constexpr const char* kAxisSettingsArrayGroup = "JoystickAxisSettingsArray";
44static constexpr const char* kAxisFunctionKey = "function";
45static constexpr const char* kAxisMinKey = "min";
46static constexpr const char* kAxisMaxKey = "max";
47static constexpr const char* kAxisCenterKey = "center";
48static constexpr const char* kAxisDeadbandKey = "deadband";
49static constexpr const char* kAxisReversedKey = "reversed";
50}
51
52/*===========================================================================*/
53
54AssignedButtonAction::AssignedButtonAction(const QString &actionName_, bool repeat_)
55 : actionName(actionName_)
56 , repeat(repeat_)
57{
58 qCDebug(JoystickVerboseLog) << this;
59}
60
61AvailableButtonAction::AvailableButtonAction(const QString &actionName_, std::function<void()> onDown_, std::function<void()> onUp_, std::function<void()> onRepeat_, QObject *parent)
62 : QObject(parent)
63 , _actionName(actionName_)
64 , _onDown(std::move(onDown_))
65 , _onRepeat(std::move(onRepeat_))
66 , _onUp(std::move(onUp_))
67{
68 qCDebug(JoystickVerboseLog) << this;
69}
70
71/*===========================================================================*/
72
73Joystick::Joystick(const QString &name, int axisCount, int buttonCount, int hatCount, QObject *parent)
74 : QThread(parent)
75 , _name(name)
76 , _axisCount(axisCount)
77 , _buttonCount(buttonCount)
78 , _hatCount(hatCount)
79 , _hatButtonCount(4 * hatCount)
80 , _totalButtonCount(_buttonCount + _hatButtonCount)
81 , _rgCalibration(_axisCount)
82 , _buttonEventStates(_totalButtonCount)
83 , _assignedButtonActions(_totalButtonCount, nullptr)
84 , _mavlinkActionManager(new MavlinkActionManager(SettingsManager::instance()->mavlinkActionsSettings()->joystickActionsFile(), this))
85 , _availableButtonActions(new QmlObjectListModel(this))
86 , _joystickManager(JoystickManager::instance())
87 , _joystickSettings(name, _axisCount, _totalButtonCount)
88{
89 qCDebug(JoystickLog)
90 << name
91 << "axisCount:" << axisCount
92 << "buttonCount:" << buttonCount
93 << "hatCount:" << hatCount
94 << this;
95
96 if (QCoreApplication *const app = QCoreApplication::instance()) {
97 QThread *const guiThread = app->thread();
98 if (_joystickSettings.thread() != guiThread) {
99 _joystickSettings.moveToThread(guiThread);
100 }
101
102 const auto ensureFactThread = [guiThread](Fact *fact) {
103 if (fact && fact->thread() != guiThread) {
104 fact->moveToThread(guiThread);
105 }
106 };
107
108 ensureFactThread(_joystickSettings.calibrated());
109 ensureFactThread(_joystickSettings.circleCorrection());
110 ensureFactThread(_joystickSettings.useDeadband());
111 ensureFactThread(_joystickSettings.negativeThrust());
112 ensureFactThread(_joystickSettings.throttleSmoothing());
113 ensureFactThread(_joystickSettings.axisFrequencyHz());
114 ensureFactThread(_joystickSettings.buttonFrequencyHz());
115 ensureFactThread(_joystickSettings.throttleModeCenterZero());
116 ensureFactThread(_joystickSettings.transmitterMode());
117 ensureFactThread(_joystickSettings.exponentialPct());
118 ensureFactThread(_joystickSettings.enableManualControlPitchExtension());
119 ensureFactThread(_joystickSettings.enableManualControlRollExtension());
120 ensureFactThread(_joystickSettings.enableAdditionalAxis1());
121 ensureFactThread(_joystickSettings.enableAdditionalAxis2());
122 ensureFactThread(_joystickSettings.enableAdditionalAxis3());
123 ensureFactThread(_joystickSettings.enableAdditionalAxis4());
124 ensureFactThread(_joystickSettings.enableAdditionalAxis5());
125 ensureFactThread(_joystickSettings.enableAdditionalAxis6());
126 ensureFactThread(_joystickSettings.additionalAxesFunction());
127 }
128
129 // Changes to manual control extension settings require re-calibration
130 connect(_joystickSettings.enableManualControlPitchExtension(), &Fact::rawValueChanged, this, [this]() {
131 _joystickSettings.calibrated()->setRawValue(false);
132 });
133 connect(_joystickSettings.enableManualControlRollExtension(), &Fact::rawValueChanged, this, [this]() {
134 _joystickSettings.calibrated()->setRawValue(false);
135 });
136 connect(_joystickSettings.enableAdditionalAxis1(), &Fact::rawValueChanged, this, [this]() {
137 _joystickSettings.calibrated()->setRawValue(false);
138 });
139 connect(_joystickSettings.enableAdditionalAxis2(), &Fact::rawValueChanged, this, [this]() {
140 _joystickSettings.calibrated()->setRawValue(false);
141 });
142 connect(_joystickSettings.enableAdditionalAxis3(), &Fact::rawValueChanged, this, [this]() {
143 _joystickSettings.calibrated()->setRawValue(false);
144 });
145 connect(_joystickSettings.enableAdditionalAxis4(), &Fact::rawValueChanged, this, [this]() {
146 _joystickSettings.calibrated()->setRawValue(false);
147 });
148 connect(_joystickSettings.enableAdditionalAxis5(), &Fact::rawValueChanged, this, [this]() {
149 _joystickSettings.calibrated()->setRawValue(false);
150 });
151 connect(_joystickSettings.enableAdditionalAxis6(), &Fact::rawValueChanged, this, [this]() {
152 _joystickSettings.calibrated()->setRawValue(false);
153 });
154
155 _resetFunctionToAxisMap();
156 _resetAxisCalibrationData();
157 _resetButtonActionData();
158 _resetButtonEventStates();
159
160 _buildAvailableButtonsActionList(MultiVehicleManager::instance()->activeVehicle());
161
162 _loadFromSettingsIntoCalibrationData();
163}
164
166{
167 _exitPollingThread = true;
168 if (isRunning()) {
169 if (QThread::currentThread() == this) {
170 qCWarning(JoystickLog) << "Skipping wait() on joystick thread";
171 } else {
172 wait();
173 }
174 }
175
176 _resetButtonActionData();
177 _availableButtonActions->clearAndDeleteContents();
178}
179
180void Joystick::_resetFunctionToAxisMap()
181{
182 _axisFunctionToJoystickAxisMap.clear();
183 for (int i = 0; i < maxAxisFunction; i++) {
184 _axisFunctionToJoystickAxisMap[static_cast<AxisFunction_t>(i)] = kJoystickAxisNotAssigned;
185 }
186}
187
188void Joystick::_resetAxisCalibrationData()
189{
190 _resetFunctionToAxisMap();
191 for (int axis = 0; axis < _axisCount; axis++) {
192 auto& calibration = _rgCalibration[axis];
193 calibration.reset();
194 }
195}
196
197void Joystick::_resetButtonActionData()
198{
199 for (int i = 0; i < _assignedButtonActions.count(); i++) {
200 delete _assignedButtonActions[i];
201 _assignedButtonActions[i] = nullptr;
202 }
203}
204
205void Joystick::_resetButtonEventStates()
206{
207 for (int i = 0; i < _buttonEventStates.count(); i++) {
208 _buttonEventStates[i] = ButtonEventNone;
209 }
210}
211
212void Joystick::_loadButtonSettings()
213{
214 const QString actionNameKey = QString::fromLatin1(kButtonActionNameKey);
215 const QString repeatKey = QString::fromLatin1(kButtonRepeatKey);
216
217 QSettings buttonSettings;
218 buttonSettings.beginGroup(_joystickSettings.settingsGroup());
219
220 if (buttonSettings.childGroups().contains(QString::fromLatin1(kButtonActionArrayGroup))) {
221 buttonSettings.beginGroup(QString::fromLatin1(kButtonActionArrayGroup));
222
223 qCDebug(JoystickLog) << "Button Actions:";
224
225 bool foundButton = false;
226 for (int buttonIndex = 0; buttonIndex < _totalButtonCount; buttonIndex++) {
227 if (!buttonSettings.childGroups().contains(QString::number(buttonIndex))) {
228 continue;
229 }
230
231 buttonSettings.beginGroup(QString::number(buttonIndex));
232 const QString actionName = buttonSettings.value(actionNameKey).toString();
233 const bool repeat = buttonSettings.value(repeatKey, false).toBool();
234 buttonSettings.endGroup();
235
236 if (actionName.isEmpty()) {
237 qCWarning(JoystickLog)
238 << " "
239 << "button:" << buttonIndex
240 << "has empty action name, clearing action from data";
241 buttonSettings.remove(QString::number(buttonIndex));
242 continue;
243 }
244 if (_findAvailableButtonActionIndex(actionName) == -1) {
245 // The action may not be available right now but may become available later.
246 // For example flight modes can change dynamically based on the standard modes
247 // protocol and are only known once a vehicle is connected. Keep the assignment
248 // and add it to the available actions list so the UI always shows it;
249 // _executeButtonAction() checks real availability at button press time.
250 qCDebug(JoystickLog)
251 << " "
252 << "button:" << buttonIndex
253 << "action name not currently available:" << actionName
254 << ", keeping assignment";
255 _addAvailableButtonActionIfMissing(actionName);
256 }
257 if (actionName == _buttonActionNone) {
258 buttonSettings.remove(QString::number(buttonIndex));
259 continue;
260 }
261
262 AssignedButtonAction *buttonAction = new AssignedButtonAction(actionName, repeat);
263 _assignedButtonActions[buttonIndex] = buttonAction;
264 _assignedButtonActions[buttonIndex]->buttonElapsedTimer.start();
265 foundButton = true;
266
267 qCDebug(JoystickLog)
268 << " "
269 << "button:" << buttonIndex
270 << "actionName:" << actionName
271 << "repeat:" << repeat;
272 }
273
274 buttonSettings.endGroup();
275
276 if (!foundButton) {
277 buttonSettings.remove(QString::fromLatin1(kButtonActionArrayGroup));
278 }
279 }
280}
281
282void Joystick::_foundInvalidAxisSettingsCleanup()
283{
284 _clearAxisSettings();
285 _resetAxisCalibrationData();
286 _joystickSettings.calibrated()->setRawValue(false);
287}
288
289void Joystick::_loadAxisSettings(bool joystickCalibrated, int transmitterMode)
290{
291 QSettings axisSettings;
292 axisSettings.beginGroup(_joystickSettings.settingsGroup());
293
294 // If the joystick isn't calibrated, we skip loading calibration data.
295 // Also there shouldn't be stored axis information, so clear that if any
296 if (!joystickCalibrated) {
297 _clearAxisSettings();
298 _resetAxisCalibrationData();
299 return;
300 }
301
302 const QString functionKey = QString::fromLatin1(kAxisFunctionKey);
303 const QString minKey = QString::fromLatin1(kAxisMinKey);
304 const QString maxKey = QString::fromLatin1(kAxisMaxKey);
305 const QString centerKey = QString::fromLatin1(kAxisCenterKey);
306 const QString deadbandKey = QString::fromLatin1(kAxisDeadbandKey);
307 const QString reversedKey = QString::fromLatin1(kAxisReversedKey);
308
309 qCDebug(JoystickLog) << "Axis Settings:";
310
311 axisSettings.beginGroup(QString::fromLatin1(kAxisSettingsArrayGroup));
312 for (int axis = 0; axis < _axisCount; axis++) {
313 if (!axisSettings.childGroups().contains(QString::number(axis))) {
314 qCDebug(JoystickLog)
315 << " "
316 << "axis:" << axis
317 << "no settings found, skipping";
318 continue;
319 }
320
321 axisSettings.beginGroup(QString::number(axis));
322
323 auto& axisCalibration = _rgCalibration[axis];
324
325 const int axisFunction = axisSettings.value(functionKey, maxAxisFunction).toInt();
326 if (axisFunction < 0 || axisFunction > maxAxisFunction) {
327 qCWarning(JoystickLog) << "Invalid function" << axisFunction << "for axis" << axis;
328 axisSettings.endGroup();
329 continue;
330 }
331 if (axisFunction == maxAxisFunction) {
332 // Older code would save unassigned axes with maxAxisFunction, we now skip loading those since that is not a valid function assignment
333 axisSettings.endGroup();
334 continue;
335 }
336 _setJoystickAxisForAxisFunction(static_cast<AxisFunction_t>(axisFunction), axis);
337
338 axisCalibration.center = axisSettings.value(centerKey, axisCalibration.center).toInt();
339 axisCalibration.min = axisSettings.value(minKey, axisCalibration.min).toInt();
340 axisCalibration.max = axisSettings.value(maxKey, axisCalibration.max).toInt();
341 axisCalibration.deadband = axisSettings.value(deadbandKey, axisCalibration.deadband).toInt();
342 axisCalibration.reversed = axisSettings.value(reversedKey, axisCalibration.reversed).toBool();
343
344 qCDebug(JoystickLog)
345 << " "
346 << "axis:" << axis
347 << "min:" << axisCalibration.min
348 << "max:" << axisCalibration.max
349 << "center:" << axisCalibration.center
350 << "reversed:" << axisCalibration.reversed
351 << "deadband:" << axisCalibration.deadband
352 << "axisFunction:" << axisFunctionToString(static_cast<AxisFunction_t>(axisFunction));
353
354 axisSettings.endGroup();
355 }
356 axisSettings.endGroup();
357
358 // All axis information is loaded, verify that we have all the required control mappings
359 bool rollFunctionNotAssigned = _axisFunctionToJoystickAxisMap[rollFunction] == kJoystickAxisNotAssigned;
360 if (rollFunctionNotAssigned) {
361 qCWarning(JoystickLog) << "Roll axis function not assigned";
362 }
363 bool pitchFunctionNotAssigned = _axisFunctionToJoystickAxisMap[pitchFunction] == kJoystickAxisNotAssigned;
364 if (pitchFunctionNotAssigned) {
365 qCWarning(JoystickLog) << "Pitch axis function not assigned";
366 }
367 bool yawFunctionNotAssigned = _axisFunctionToJoystickAxisMap[yawFunction] == kJoystickAxisNotAssigned;
368 if (yawFunctionNotAssigned) {
369 qCWarning(JoystickLog) << "Yaw axis function not assigned";
370 }
371 bool throttleFunctionNotAssigned = _axisFunctionToJoystickAxisMap[throttleFunction] == kJoystickAxisNotAssigned;
372 if (throttleFunctionNotAssigned) {
373 qCWarning(JoystickLog) << "Throttle axis function not assigned";
374 }
375 bool pitchExtensionFunctionRequiredButNotAssigned = false;
376 if (_joystickSettings.enableManualControlPitchExtension()->rawValue().toBool()) {
377 pitchExtensionFunctionRequiredButNotAssigned = _getJoystickAxisForAxisFunction(pitchExtensionFunction) == kJoystickAxisNotAssigned;
378 if (pitchExtensionFunctionRequiredButNotAssigned) {
379 qCWarning(JoystickLog) << "Internal Error: Missing pitch extension axis function mapping!";
380 }
381 }
382 bool rollExtensionFunctionRequiredButNotAssigned = false;
383 if (_joystickSettings.enableManualControlRollExtension()->rawValue().toBool()) {
384 rollExtensionFunctionRequiredButNotAssigned = _getJoystickAxisForAxisFunction(rollExtensionFunction) == kJoystickAxisNotAssigned;
385 if (rollExtensionFunctionRequiredButNotAssigned) {
386 qCWarning(JoystickLog) << "Internal Error: Missing roll extension axis function mapping!";
387 }
388 }
389 bool additionalAxis1FunctionRequiredButNotAssigned = false;
390 if (_joystickSettings.enableAdditionalAxis1()->rawValue().toBool()) {
391 additionalAxis1FunctionRequiredButNotAssigned = _getJoystickAxisForAxisFunction(additionalAxis1Function) == kJoystickAxisNotAssigned;
392 if (additionalAxis1FunctionRequiredButNotAssigned) {
393 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 1 function mapping!";
394 }
395 }
396 bool additionalAxis2FunctionRequiredButNotAssigned = false;
397 if (_joystickSettings.enableAdditionalAxis2()->rawValue().toBool()) {
398 additionalAxis2FunctionRequiredButNotAssigned = _getJoystickAxisForAxisFunction(additionalAxis2Function) == kJoystickAxisNotAssigned;
399 if (additionalAxis2FunctionRequiredButNotAssigned) {
400 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 2 function mapping!";
401 }
402 }
403 bool additionalAxis3FunctionRequiredButNotAssigned = false;
404 if (_joystickSettings.enableAdditionalAxis3()->rawValue().toBool()) {
405 additionalAxis3FunctionRequiredButNotAssigned = _getJoystickAxisForAxisFunction(additionalAxis3Function) == kJoystickAxisNotAssigned;
406 if (additionalAxis3FunctionRequiredButNotAssigned) {
407 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 3 function mapping!";
408 }
409 }
410 bool additionalAxis4FunctionRequiredButNotAssigned = false;
411 if (_joystickSettings.enableAdditionalAxis4()->rawValue().toBool()) {
412 additionalAxis4FunctionRequiredButNotAssigned = _getJoystickAxisForAxisFunction(additionalAxis4Function) == kJoystickAxisNotAssigned;
413 if (additionalAxis4FunctionRequiredButNotAssigned) {
414 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 4 function mapping!";
415 }
416 }
417 bool additionalAxis5FunctionRequiredButNotAssigned = false;
418 if (_joystickSettings.enableAdditionalAxis5()->rawValue().toBool()) {
419 additionalAxis5FunctionRequiredButNotAssigned = _getJoystickAxisForAxisFunction(additionalAxis5Function) == kJoystickAxisNotAssigned;
420 if (additionalAxis5FunctionRequiredButNotAssigned) {
421 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 5 function mapping!";
422 }
423 }
424 bool additionalAxis6FunctionRequiredButNotAssigned = false;
425 if (_joystickSettings.enableAdditionalAxis6()->rawValue().toBool()) {
426 additionalAxis6FunctionRequiredButNotAssigned = _getJoystickAxisForAxisFunction(additionalAxis6Function) == kJoystickAxisNotAssigned;
427 if (additionalAxis6FunctionRequiredButNotAssigned) {
428 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 6 function mapping!";
429 }
430 }
431 if (rollFunctionNotAssigned || pitchFunctionNotAssigned || yawFunctionNotAssigned || throttleFunctionNotAssigned ||
432 pitchExtensionFunctionRequiredButNotAssigned || rollExtensionFunctionRequiredButNotAssigned ||
433 additionalAxis1FunctionRequiredButNotAssigned || additionalAxis2FunctionRequiredButNotAssigned || additionalAxis3FunctionRequiredButNotAssigned ||
434 additionalAxis4FunctionRequiredButNotAssigned || additionalAxis5FunctionRequiredButNotAssigned || additionalAxis6FunctionRequiredButNotAssigned) {
435 qCWarning(JoystickLog) << "Missing control axis function(s), resetting all axis settings, marking joystick as uncalibrated and disabled";
436 _resetAxisCalibrationData();
437 _clearAxisSettings();
438 _joystickSettings.calibrated()->setRawValue(false);
439 _joystickManager->setActiveJoystickEnabledForActiveVehicle(false);
440 return;
441 }
442
443 // FunctionAxis mappings are always stored in TX mode 2
444 // Remap to stored TX mode in settings for UI display
445 _remapFunctionsInFunctionMapToNewTransmittedMode(2, transmitterMode);
446}
447
448void Joystick::_loadFromSettingsIntoCalibrationData()
449{
450 _resetAxisCalibrationData();
451 _resetButtonActionData();
452
453 // Top level joystick settings come through the JoystickSettings SettingsGroup
454
455 bool calibrated = _joystickSettings.calibrated()->rawValue().toBool();
456 int transmitterMode = _joystickSettings.transmitterMode()->rawValue().toInt();
457
458 qCDebug(JoystickLog) << name();
459 qCDebug(JoystickLog) << " calibrated:" << _joystickSettings.calibrated()->rawValue().toBool();
460 qCDebug(JoystickLog) << " throttleSmoothing:" << _joystickSettings.throttleSmoothing()->rawValue().toBool();
461 qCDebug(JoystickLog) << " axisFrequencyHz:" << _joystickSettings.axisFrequencyHz()->rawValue().toDouble();
462 qCDebug(JoystickLog) << " buttonFrequencyHz:" << _joystickSettings.buttonFrequencyHz()->rawValue().toDouble();
463 qCDebug(JoystickLog) << " throttleModeCenterZero:" << _joystickSettings.throttleModeCenterZero()->rawValue().toBool();
464 qCDebug(JoystickLog) << " negativeThrust:" << _joystickSettings.negativeThrust()->rawValue().toBool();
465 qCDebug(JoystickLog) << " circleCorrection:" << _joystickSettings.circleCorrection()->rawValue().toBool();
466 qCDebug(JoystickLog) << " exponentialPct:" << _joystickSettings.exponentialPct()->rawValue().toDouble();
467 qCDebug(JoystickLog) << " useDeadband:" << _joystickSettings.useDeadband()->rawValue().toBool();
468 qCDebug(JoystickLog) << " transmitterMode:" << transmitterMode;
469 qCDebug(JoystickLog) << " enableManualControlPitchExtension:" << _joystickSettings.enableManualControlPitchExtension()->rawValue().toBool();
470 qCDebug(JoystickLog) << " enableManualControlRollExtension:" << _joystickSettings.enableManualControlRollExtension()->rawValue().toBool();
471 qCDebug(JoystickLog) << " additionalAxesFunction:" << (_joystickSettings.additionalAxesFunction()->rawValue().toUInt() == 1 ? "RC_CHANNELS_OVERRIDE" : "MANUAL_CONTROL");
472 qCDebug(JoystickLog) << " enableAdditionalAxis1:" << _joystickSettings.enableAdditionalAxis1()->rawValue().toBool();
473 qCDebug(JoystickLog) << " enableAdditionalAxis2:" << _joystickSettings.enableAdditionalAxis2()->rawValue().toBool();
474 qCDebug(JoystickLog) << " enableAdditionalAxis3:" << _joystickSettings.enableAdditionalAxis3()->rawValue().toBool();
475 qCDebug(JoystickLog) << " enableAdditionalAxis4:" << _joystickSettings.enableAdditionalAxis4()->rawValue().toBool();
476 qCDebug(JoystickLog) << " enableAdditionalAxis5:" << _joystickSettings.enableAdditionalAxis5()->rawValue().toBool();
477 qCDebug(JoystickLog) << " enableAdditionalAxis6:" << _joystickSettings.enableAdditionalAxis6()->rawValue().toBool();
478
479 _loadAxisSettings(calibrated, transmitterMode);
480 _loadButtonSettings();
481}
482
483void Joystick::_saveAxisSettings(int transmitterMode)
484{
485 qCDebug(JoystickLog) << name();
486
487 // FunctionAxis mappings are always stored in TX mode 2
488 // Temporarily remap from calibration TX mode to mode 2 for storage
489 _remapFunctionsInFunctionMapToNewTransmittedMode(transmitterMode, 2);
490
491 QSettings axisSettings;
492 axisSettings.beginGroup(_joystickSettings.settingsGroup());
493 axisSettings.beginGroup(QString::fromLatin1(kAxisSettingsArrayGroup));
494
495 const QString functionKey = QString::fromLatin1(kAxisFunctionKey);
496 const QString minKey = QString::fromLatin1(kAxisMinKey);
497 const QString maxKey = QString::fromLatin1(kAxisMaxKey);
498 const QString centerKey = QString::fromLatin1(kAxisCenterKey);
499 const QString deadbandKey = QString::fromLatin1(kAxisDeadbandKey);
500 const QString reversedKey = QString::fromLatin1(kAxisReversedKey);
501
502 for (int axis = 0; axis < _axisCount; axis++) {
503 AxisFunction_t function = _getAxisFunctionForJoystickAxis(axis);
504 if (function == maxAxisFunction) {
505 // No function assigned to axis, nothing to save
506 continue;
507 }
508 AxisCalibration_t *const calibration = &_rgCalibration[axis];
509 axisSettings.beginGroup(QString::number(axis));
510
511 axisSettings.setValue(centerKey, calibration->center);
512 axisSettings.setValue(minKey, calibration->min);
513 axisSettings.setValue(maxKey, calibration->max);
514 axisSettings.setValue(deadbandKey, calibration->deadband);
515 axisSettings.setValue(reversedKey, calibration->reversed);
516 axisSettings.setValue(functionKey, static_cast<int>(function));
517
518 qCDebug(JoystickLog)
519 << " "
520 << "axis:" << axis
521 << "min:" << calibration->min
522 << "max:" << calibration->max
523 << "center:" << calibration->center
524 << "reversed:" << calibration->reversed
525 << "deadband:" << calibration->deadband
526 << "axisFunction:" << axisFunctionToString(function);
527
528 axisSettings.endGroup();
529 }
530
531 axisSettings.endGroup();
532 axisSettings.endGroup();
533
534 // Map back to current TX mode
535 _remapFunctionsInFunctionMapToNewTransmittedMode(2, transmitterMode);
536}
537
538void Joystick::_saveButtonSettings()
539{
540 qCDebug(JoystickLog) << name();
541
542 const QString actionNameKey = QString::fromLatin1(kButtonActionNameKey);
543 const QString repeatKey = QString::fromLatin1(kButtonRepeatKey);
544
545 QSettings buttonSettings;
546 buttonSettings.beginGroup(_joystickSettings.settingsGroup());
547 buttonSettings.beginGroup(QString::fromLatin1(kButtonActionArrayGroup));
548
549 bool anyButtonsSaved = false;
550 for (int buttonIndex = 0; buttonIndex < _totalButtonCount; buttonIndex++) {
551 AssignedButtonAction *buttonAction = _assignedButtonActions[buttonIndex];
552
553 if (buttonAction) {
554 buttonSettings.beginGroup(QString::number(buttonIndex));
555 buttonSettings.setValue(actionNameKey, buttonAction->actionName);
556 buttonSettings.setValue(repeatKey, buttonAction->repeat);
557 buttonSettings.endGroup();
558
559 anyButtonsSaved = true;
560
561 qCDebug(JoystickLog)
562 << " "
563 << "button:" << buttonIndex
564 << "actionName:" << buttonAction->actionName
565 << "repeat:" << buttonAction->repeat;
566 } else {
567 // No action assigned, remove any existing settings
568 buttonSettings.remove(QString::number(buttonIndex));
569 }
570 }
571
572 buttonSettings.endGroup();
573 buttonSettings.endGroup();
574
575 if (!anyButtonsSaved) {
576 qCDebug(JoystickLog) << " No button actions to save, cleared button action settings.";
577 _clearButtonSettings();
578 }
579}
580
581void Joystick::_clearAxisSettings()
582{
583 QSettings axisSettings;
584 axisSettings.beginGroup(_joystickSettings.settingsGroup());
585 axisSettings.remove(QString::fromLatin1(kAxisSettingsArrayGroup));
586 axisSettings.endGroup();
587}
588
589void Joystick::_clearButtonSettings()
590{
591 QSettings buttonSettings;
592 buttonSettings.beginGroup(_joystickSettings.settingsGroup());
593 buttonSettings.remove(QString::fromLatin1(kButtonActionArrayGroup));
594 buttonSettings.endGroup();
595}
596
597void Joystick::_saveFromCalibrationDataIntoSettings()
598{
599 bool calibrated = _joystickSettings.calibrated()->rawValue().toBool();
600 int transmitterMode = _joystickSettings.transmitterMode()->rawValue().toInt();
601
602 qCDebug(JoystickLog) << name();
603 qCDebug(JoystickLog) << " calibrated:" << calibrated;
604 qCDebug(JoystickLog) << " throttleSmoothing:" << _joystickSettings.throttleSmoothing()->rawValue().toBool();
605 qCDebug(JoystickLog) << " axisFrequencyHz:" << _joystickSettings.axisFrequencyHz()->rawValue().toDouble();
606 qCDebug(JoystickLog) << " buttonFrequencyHz:" << _joystickSettings.buttonFrequencyHz()->rawValue().toDouble();
607 qCDebug(JoystickLog) << " throttleModeCenterZero:" << _joystickSettings.throttleModeCenterZero()->rawValue().toBool();
608 qCDebug(JoystickLog) << " negativeThrust:" << _joystickSettings.negativeThrust()->rawValue().toBool();
609 qCDebug(JoystickLog) << " circleCorrection:" << _joystickSettings.circleCorrection()->rawValue().toBool();
610 qCDebug(JoystickLog) << " exponentialPct:" << _joystickSettings.exponentialPct()->rawValue().toDouble();
611 qCDebug(JoystickLog) << " useDeadband:" << _joystickSettings.useDeadband()->rawValue().toBool();
612 qCDebug(JoystickLog) << " enableManualControlPitchExtension:" << _joystickSettings.enableManualControlPitchExtension()->rawValue().toBool();
613 qCDebug(JoystickLog) << " enableManualControlRollExtension:" << _joystickSettings.enableManualControlRollExtension()->rawValue().toBool();
614 qCDebug(JoystickLog) << " additionalAxesFunction:" << (_joystickSettings.additionalAxesFunction()->rawValue().toUInt() == 1 ? "RC_CHANNELS_OVERRIDE" : "MANUAL_CONTROL");
615 qCDebug(JoystickLog) << " enableAdditionalAxis1:" << _joystickSettings.enableAdditionalAxis1()->rawValue().toBool();
616 qCDebug(JoystickLog) << " enableAdditionalAxis2:" << _joystickSettings.enableAdditionalAxis2()->rawValue().toBool();
617 qCDebug(JoystickLog) << " enableAdditionalAxis3:" << _joystickSettings.enableAdditionalAxis3()->rawValue().toBool();
618 qCDebug(JoystickLog) << " enableAdditionalAxis4:" << _joystickSettings.enableAdditionalAxis4()->rawValue().toBool();
619 qCDebug(JoystickLog) << " enableAdditionalAxis5:" << _joystickSettings.enableAdditionalAxis5()->rawValue().toBool();
620 qCDebug(JoystickLog) << " enableAdditionalAxis6:" << _joystickSettings.enableAdditionalAxis6()->rawValue().toBool();
621 qCDebug(JoystickLog) << " transmitterMode:" << transmitterMode;
622
623 _clearAxisSettings();
624 if (calibrated) {
625 _saveAxisSettings(transmitterMode);
626 }
627
628 _clearButtonSettings();
629 _saveButtonSettings();
630}
631
632void Joystick::_remapFunctionsInFunctionMapToNewTransmittedMode(int fromMode, int toMode)
633{
634 static constexpr const int modeCount = 4;
635 static constexpr const int attitudeControlCount = 4;
636 static constexpr const AxisFunction_t mapping[modeCount][attitudeControlCount] = {
641 };
642
643 // First make a direct copy so the extension stick function are set correctly
644 AxisFunctionMap_t tempMap = _axisFunctionToJoystickAxisMap;
645
646 for (int i=0; i<attitudeControlCount; i++) {
647 auto fromAxisFunction = mapping[fromMode - 1][i];
648 auto toAxisFunction = mapping[toMode - 1][i];
649
650 tempMap[toAxisFunction] = _axisFunctionToJoystickAxisMap[fromAxisFunction];
651 }
652
653 for (int i = 0; i < maxAxisFunction; i++) {
654 auto axisFunction = static_cast<AxisFunction_t>(i);
655 _axisFunctionToJoystickAxisMap[axisFunction] = tempMap[axisFunction];
656 }
657}
658
659void Joystick::run()
660{
661 bool openFailed = false;
662 bool updateFailed = false;
663
664 if (!_open()) {
665 qCWarning(JoystickLog) << "Failed to open joystick:" << _name;
666 openFailed = true;
667 }
668
669 if (!openFailed) {
670 _axisElapsedTimer.start();
671
672 for (int buttonIndex = 0; buttonIndex < _totalButtonCount; buttonIndex++) {
673 if (_assignedButtonActions[buttonIndex]) {
674 _assignedButtonActions[buttonIndex]->buttonElapsedTimer.start();
675 }
676 }
677
678 while (!_exitPollingThread) {
679 if (!_update()) {
680 qCWarning(JoystickLog) << "Joystick disconnected or update failed:" << _name;
681 updateFailed = true;
682 break;
683 }
684
685 _handleButtons();
686
687 if (axisCount() != 0) {
688 _handleAxis();
689 }
690
691 const double axisFrequencyHz = _joystickSettings.axisFrequencyHz()->rawValue().toDouble();
692 const double buttonFrequencyHz = _joystickSettings.buttonFrequencyHz()->rawValue().toDouble();
693
694 const int sleep = qMin(static_cast<int>(1000.0 / axisFrequencyHz), static_cast<int>(1000.0 / buttonFrequencyHz)) / 2;
695 QThread::msleep(sleep);
696 }
697
698 _close();
699 }
700
701 if ((openFailed || updateFailed) && !_exitPollingThread) {
702 qCDebug(JoystickLog) << "Triggering joystick rescan after failure";
703 QMetaObject::invokeMethod(JoystickManager::instance(), "_checkForAddedOrRemovedJoysticks", Qt::QueuedConnection);
704 }
705}
706
707void Joystick::_updateButtonEventState(int buttonIndex, const bool buttonPressed, ButtonEvent_t &buttonEventState)
708{
709 if (buttonPressed) {
710 if (buttonEventState == ButtonEventNone) {
711 qCDebug(JoystickLog) << "Button" << buttonIndex << "down transition";
712 buttonEventState = ButtonEventDownTransition;
713 } else if (buttonEventState == ButtonEventDownTransition) {
714 qCDebug(JoystickLog) << "Button" << buttonIndex << "repeat";
715 buttonEventState = ButtonEventRepeat;
716 }
717 } else {
718 if (buttonEventState == ButtonEventDownTransition || buttonEventState == ButtonEventRepeat) {
719 qCDebug(JoystickLog) << "Button" << buttonIndex << "up transition";
720 buttonEventState = ButtonEventUpTransition;
721 } else if (buttonEventState == ButtonEventUpTransition) {
722 qCDebug(JoystickLog) << "Button" << buttonIndex << "none";
723 buttonEventState = ButtonEventNone;
724 }
725 }
726}
727
728void Joystick::_updateButtonEventStates(QVector<ButtonEvent_t> &buttonEventStates)
729{
730 if (buttonEventStates.size() < _totalButtonCount) {
731 qCWarning(JoystickLog) << "Internal Error: buttonEventStates size incorrect!";
732 return;
733 }
734
735 // Update standard buttons - index 0 to buttonCount-1
736 for (int buttonIndex = 0; buttonIndex < _buttonCount; buttonIndex++) {
737 const bool buttonIsPressed = _getButton(buttonIndex);
738 _updateButtonEventState(buttonIndex, buttonIsPressed, buttonEventStates[buttonIndex]);
739 }
740
741 // Update hat buttons - indexes buttonCount to totalButtonCount-1
742 const int numHatButtons = 4;
743 int nextIndex = _buttonCount;
744 for (int hatIndex = 0; hatIndex < _hatCount; hatIndex++) {
745 for (int hatButtonIndex = 0; hatButtonIndex < numHatButtons; hatButtonIndex++) {
746 const bool buttonIsPressed = _getHat(hatIndex, hatButtonIndex);
747 const int currentIndex = nextIndex;
748 _updateButtonEventState(currentIndex, buttonIsPressed, buttonEventStates[currentIndex]);
749 ++nextIndex;
750 }
751 }
752}
753
754void Joystick::_handleButtons()
755{
756 if (_pollingFlags == PollingNone) {
757 qCWarning(JoystickLog) << "Internal Error: Joystick not polling!";
758 return;
759 }
760
761 _updateButtonEventStates(_buttonEventStates);
762
763 if (_pollingFlags.testFlag(PollingForConfiguration)) {
764 for (int buttonIndex = 0; buttonIndex < _totalButtonCount; buttonIndex++) {
765 if (_buttonEventStates[buttonIndex] == ButtonEventDownTransition) {
766 qCDebug(JoystickLog) << "Button pressed - button" << buttonIndex;
767 emit rawButtonPressedChanged(buttonIndex, true);
768 } else if (_buttonEventStates[buttonIndex] == ButtonEventUpTransition) {
769 qCDebug(JoystickLog) << "Button released - button" << buttonIndex;
770 emit rawButtonPressedChanged(buttonIndex, false);
771 }
772 }
773 } else if (_pollingFlags.testFlag(PollingForVehicle)) {
774 Vehicle *const vehicle = _pollingVehicle;
775 if (!vehicle) {
776 qCWarning(JoystickLog) << "Internal Error: No vehicle for joystick!";
777 return;
778 }
779 if (!_joystickManager->activeJoystickEnabledForActiveVehicle()) {
780 qCWarning(JoystickLog) << "Internal Error: Joystick not enabled for vehicle!";
781 return;
782 }
783 if (!_joystickSettings.calibrated()->rawValue().toBool()) {
784 return;
785 }
786
787 //-- Process button press/release
788 const int buttonDelay = static_cast<int>(1000.0 / _joystickSettings.buttonFrequencyHz()->rawValue().toDouble());
789 QSet<QString> executedActions;
790 for (int buttonIndex = 0; buttonIndex < _totalButtonCount; buttonIndex++) {
791 if (!_assignedButtonActions[buttonIndex]) {
792 continue;
793 }
794 auto assignedAction = _assignedButtonActions[buttonIndex];
795
796 const QString buttonAction = assignedAction->actionName;
797 if (buttonAction.isEmpty() || (buttonAction == _buttonActionNone)) {
798 continue;
799 }
800
801 auto buttonEventState = _buttonEventStates[buttonIndex];
802 if (buttonEventState == ButtonEventDownTransition || buttonEventState == ButtonEventRepeat) {
803 if (assignedAction->repeat) {
804 if (assignedAction->buttonElapsedTimer.elapsed() > buttonDelay) {
805 assignedAction->buttonElapsedTimer.start();
806 qCDebug(JoystickLog) << "Repeat - button:action" << buttonIndex << buttonAction;
807 // Post to the GUI thread for safe access to class members.
808 QMetaObject::invokeMethod(this, [this, buttonAction]() {
809 _executeButtonAction(buttonAction, ButtonEventRepeat);
810 }, Qt::QueuedConnection);
811 }
812 } else {
813 if (buttonEventState == ButtonEventDownTransition) {
814 // Check for multi-button action
815 QList<int> multiActionButtons = { buttonIndex };
816 bool allActionButtonsPressed = true;
817 for (int multiIndex = 0; multiIndex < _totalButtonCount; multiIndex++) {
818 if (multiIndex == buttonIndex) {
819 continue;
820 }
821 if (_assignedButtonActions[multiIndex] && (_assignedButtonActions[multiIndex]->actionName == buttonAction)) {
822 // We found a multi-button action
823 if (_buttonEventStates[multiIndex] == ButtonEventDownTransition || _buttonEventStates[multiIndex] == ButtonEventRepeat) {
824 // So far so good
825 multiActionButtons.append(multiIndex);
826 } else {
827 // We are missing a press we need, skip this multi-button action only
828 allActionButtonsPressed = false;
829 break;
830 }
831 }
832 }
833
834 if (!allActionButtonsPressed) {
835 continue;
836 }
837
838 if (multiActionButtons.size() > 1) {
839 qCDebug(JoystickLog) << "Multi-button action - buttons:action" << multiActionButtons << buttonAction;
840 } else {
841 qCDebug(JoystickLog) << "Action triggered - button:Action" << buttonIndex << buttonAction;
842 }
843 // Multiple buttons can share the same action name (multi-button feature).
844 // Guard against executing the action more than once per poll cycle when
845 // several such buttons are all in the same event state simultaneously.
846 if (!executedActions.contains(buttonAction)) {
847 // Post to the GUI thread for safe access to class members.
848 QMetaObject::invokeMethod(this, [this, buttonAction]() {
849 _executeButtonAction(buttonAction, ButtonEventDownTransition);
850 }, Qt::QueuedConnection);
851 executedActions.insert(buttonAction);
852 }
853 continue;
854 }
855 }
856 } else if (buttonEventState == ButtonEventUpTransition) {
857 // Same deduplication guard for release events.
858 if (!executedActions.contains(buttonAction)) {
859 // Post to the GUI thread for safe access to class members.
860 QMetaObject::invokeMethod(this, [this, buttonAction]() {
861 _executeButtonAction(buttonAction, ButtonEventUpTransition);
862 }, Qt::QueuedConnection);
863 executedActions.insert(buttonAction);
864 }
865 continue;
866 }
867 }
868 }
869}
870
871float Joystick::_adjustRange(int value, const AxisCalibration_t &calibration, bool withDeadbands)
872{
873 float valueNormalized;
874 float axisLength;
875 float axisBasis;
876
877 if (calibration.center == calibration.min) {
878 axisBasis = 1.0f;
879 valueNormalized = static_cast<float>(std::max(0, value - calibration.center));
880 axisLength = calibration.max - calibration.center;
881 } else if (calibration.center == calibration.max) {
882 axisBasis = -1.0f;
883 valueNormalized = static_cast<float>(std::max(0, calibration.center - value));
884 axisLength = calibration.center - calibration.min;
885 } else if (value > calibration.center) {
886 axisBasis = 1.0f;
887 valueNormalized = value - calibration.center;
888 axisLength = calibration.max - calibration.center;
889 } else {
890 axisBasis = -1.0f;
891 valueNormalized = calibration.center - value;
892 axisLength = calibration.center - calibration.min;
893 }
894
895 if (axisLength <= 0.0f) {
896 return 0.0f;
897 }
898
899 float axisPercent;
900 if (withDeadbands) {
901 if (valueNormalized > calibration.deadband) {
902 axisPercent = (valueNormalized - calibration.deadband) / (axisLength - calibration.deadband);
903 } else if (valueNormalized<-calibration.deadband) {
904 axisPercent = (valueNormalized + calibration.deadband) / (axisLength - calibration.deadband);
905 } else {
906 axisPercent = 0.f;
907 }
908 } else {
909 axisPercent = valueNormalized / axisLength;
910 }
911
912 float correctedValue = axisBasis * axisPercent;
913 if (calibration.reversed) {
914 correctedValue *= -1.0f;
915 }
916
917 return std::max(-1.0f, std::min(correctedValue, 1.0f));
918}
919
920uint16_t Joystick::_adjustRangeToRcOverridePwm(int value, const AxisCalibration_t &calibration, bool withDeadbands)
921{
922 const float normalizedValue = _adjustRange(value, calibration, withDeadbands);
923 const bool oneSidedAxis = (calibration.center == calibration.min) || (calibration.center == calibration.max);
924
925 float pwmValue;
926 if (oneSidedAxis) {
927 pwmValue = 1000.0f + (std::clamp(normalizedValue, 0.0f, 1.0f) * 1000.0f);
928 } else {
929 pwmValue = 1500.0f + (std::clamp(normalizedValue, -1.0f, 1.0f) * 500.0f);
930 }
931
932 return static_cast<uint16_t>(std::lround(std::clamp(pwmValue, 1000.0f, 2000.0f)));
933}
934
935void Joystick::_handleAxis()
936{
937 const int axisDelay = static_cast<int>(1000.0 / _joystickSettings.axisFrequencyHz()->rawValue().toDouble());
938 if (_axisElapsedTimer.elapsed() <= axisDelay) {
939 return;
940 }
941
942 _axisElapsedTimer.start();
943
944 if (_pollingFlags == PollingNone) {
945 qCWarning(JoystickLog) << "Internal Error: Joystick not polling!";
946 return;
947 }
948
949 if (_pollingFlags.testFlag(PollingForConfiguration)) {
950 // Signal the axis values to Joystick Config/Cal. Axes in Joystick terminology are the same as Channels in
951 // RemoteControlCalibrationController terminology.
952 QVector<int> channelValues(_axisCount);
953 for (int axisIndex = 0; axisIndex < _axisCount; axisIndex++) {
954 channelValues[axisIndex] = _getAxisValue(axisIndex);
955 }
956 emit rawChannelValuesChanged(channelValues);
957 } else if (_pollingFlags.testFlag(PollingForVehicle)) {
958 Vehicle *const vehicle = _pollingVehicle;
959 if (!vehicle) {
960 qCWarning(JoystickLog) << "Internal Error: No vehicle for joystick!";
961 return;
962 }
963 if (!_joystickManager->activeJoystickEnabledForActiveVehicle()) {
964 qCWarning(JoystickLog) << "Internal Error: Joystick not enabled for vehicle!";
965 return;
966 }
967 if (!_joystickSettings.calibrated()->rawValue().toBool()) {
968 return;
969 }
970
971 bool useDeadband = _joystickSettings.useDeadband()->rawValue().toBool();
972 bool throttleModeCenterZero = _joystickSettings.throttleModeCenterZero()->rawValue().toBool();
973 bool negativeThrust = _joystickSettings.negativeThrust()->rawValue().toBool();
974 bool circleCorrection = _joystickSettings.circleCorrection()->rawValue().toBool();
975 bool throttleSmoothing = _joystickSettings.throttleSmoothing()->rawValue().toBool();
976 bool additionalAxesFunctionIsManualControl = _joystickSettings.additionalAxesFunction()->rawValue().toUInt() == 0;
977 double exponentialPercent = _joystickSettings.exponentialPct()->rawValue().toDouble();
978
979 if (_getJoystickAxisForAxisFunction(rollFunction) == kJoystickAxisNotAssigned ||
980 _getJoystickAxisForAxisFunction(pitchFunction) == kJoystickAxisNotAssigned ||
981 _getJoystickAxisForAxisFunction(yawFunction) == kJoystickAxisNotAssigned ||
982 _getJoystickAxisForAxisFunction(throttleFunction) == kJoystickAxisNotAssigned) {
983 qCWarning(JoystickLog) << "Internal Error: Missing attitude control axis function mapping!";
984 return;
985 }
986 int axisIndex = _getJoystickAxisForAxisFunction(rollFunction);
987 float roll = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
988 axisIndex = _getJoystickAxisForAxisFunction(pitchFunction);
989 float pitch = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
990 axisIndex = _getJoystickAxisForAxisFunction(yawFunction);
991 float yaw = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex],useDeadband);
992 axisIndex = _getJoystickAxisForAxisFunction(throttleFunction);
993 float throttle = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], throttleModeCenterZero ? useDeadband : false);
994
995 float pitchExtension = qQNaN();
996 if (_joystickSettings.enableManualControlPitchExtension()->rawValue().toBool()) {
997 if (_getJoystickAxisForAxisFunction(pitchExtensionFunction) == kJoystickAxisNotAssigned) {
998 qCWarning(JoystickLog) << "Internal Error: Missing pitch extension axis function mapping!";
999 return;
1000 }
1001 axisIndex = _getJoystickAxisForAxisFunction(pitchExtensionFunction);
1002 pitchExtension = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1003 }
1004 float rollExtension = qQNaN();
1005 if (_joystickSettings.enableManualControlRollExtension()->rawValue().toBool()) {
1006 if (_getJoystickAxisForAxisFunction(rollExtensionFunction) == kJoystickAxisNotAssigned) {
1007 qCWarning(JoystickLog) << "Internal Error: Missing roll extension axis function mapping!";
1008 return;
1009 }
1010 axisIndex = _getJoystickAxisForAxisFunction(rollExtensionFunction);
1011 rollExtension = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1012 }
1013 std::array<uint16_t, 6> auxRcOverridePwm{};
1014 std::array<bool, 6> auxRcOverrideEnabled{};
1015
1016 float auxManualControl1 = qQNaN();
1017 if (_joystickSettings.enableAdditionalAxis1()->rawValue().toBool()) {
1018 if (_getJoystickAxisForAxisFunction(additionalAxis1Function) == kJoystickAxisNotAssigned) {
1019 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 1 function mapping!";
1020 return;
1021 }
1022 axisIndex = _getJoystickAxisForAxisFunction(additionalAxis1Function);
1023 if (additionalAxesFunctionIsManualControl) {
1024 auxManualControl1 = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1025 } else {
1026 auxRcOverridePwm[0] = _adjustRangeToRcOverridePwm(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1027 auxRcOverrideEnabled[0] = true;
1028 }
1029 }
1030 float auxManualControl2 = qQNaN();
1031 if (_joystickSettings.enableAdditionalAxis2()->rawValue().toBool()) {
1032 if (_getJoystickAxisForAxisFunction(additionalAxis2Function) == kJoystickAxisNotAssigned) {
1033 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 2 function mapping!";
1034 return;
1035 }
1036 axisIndex = _getJoystickAxisForAxisFunction(additionalAxis2Function);
1037 if (additionalAxesFunctionIsManualControl) {
1038 auxManualControl2 = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1039 } else {
1040 auxRcOverridePwm[1] = _adjustRangeToRcOverridePwm(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1041 auxRcOverrideEnabled[1] = true;
1042 }
1043 }
1044 float auxManualControl3 = qQNaN();
1045 if (_joystickSettings.enableAdditionalAxis3()->rawValue().toBool()) {
1046 if (_getJoystickAxisForAxisFunction(additionalAxis3Function) == kJoystickAxisNotAssigned) {
1047 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 3 function mapping!";
1048 return;
1049 }
1050 axisIndex = _getJoystickAxisForAxisFunction(additionalAxis3Function);
1051 if (additionalAxesFunctionIsManualControl) {
1052 auxManualControl3 = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1053 } else {
1054 auxRcOverridePwm[2] = _adjustRangeToRcOverridePwm(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1055 auxRcOverrideEnabled[2] = true;
1056 }
1057 }
1058 float auxManualControl4 = qQNaN();
1059 if (_joystickSettings.enableAdditionalAxis4()->rawValue().toBool()) {
1060 if (_getJoystickAxisForAxisFunction(additionalAxis4Function) == kJoystickAxisNotAssigned) {
1061 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 4 function mapping!";
1062 return;
1063 }
1064 axisIndex = _getJoystickAxisForAxisFunction(additionalAxis4Function);
1065 if (additionalAxesFunctionIsManualControl) {
1066 auxManualControl4 = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1067 } else {
1068 auxRcOverridePwm[3] = _adjustRangeToRcOverridePwm(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1069 auxRcOverrideEnabled[3] = true;
1070 }
1071 }
1072 float auxManualControl5 = qQNaN();
1073 if (_joystickSettings.enableAdditionalAxis5()->rawValue().toBool()) {
1074 if (_getJoystickAxisForAxisFunction(additionalAxis5Function) == kJoystickAxisNotAssigned) {
1075 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 5 function mapping!";
1076 return;
1077 }
1078 axisIndex = _getJoystickAxisForAxisFunction(additionalAxis5Function);
1079 if (additionalAxesFunctionIsManualControl) {
1080 auxManualControl5 = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1081 } else {
1082 auxRcOverridePwm[4] = _adjustRangeToRcOverridePwm(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1083 auxRcOverrideEnabled[4] = true;
1084 }
1085 }
1086 float auxManualControl6 = qQNaN();
1087 if (_joystickSettings.enableAdditionalAxis6()->rawValue().toBool()) {
1088 if (_getJoystickAxisForAxisFunction(additionalAxis6Function) == kJoystickAxisNotAssigned) {
1089 qCWarning(JoystickLog) << "Internal Error: Missing additional axis 6 function mapping!";
1090 return;
1091 }
1092 axisIndex = _getJoystickAxisForAxisFunction(additionalAxis6Function);
1093 if (additionalAxesFunctionIsManualControl) {
1094 auxManualControl6 = _adjustRange(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1095 } else {
1096 auxRcOverridePwm[5] = _adjustRangeToRcOverridePwm(_getAxisValue(axisIndex), _rgCalibration[axisIndex], useDeadband);
1097 auxRcOverrideEnabled[5] = true;
1098 }
1099 }
1100
1101 if (throttleSmoothing) {
1102 static float throttleSmoothingValue = 0.f;
1103 throttleSmoothingValue += (throttle * (40 / 1000.f)); // for throttle to change from min to max it will take 1000ms (40ms is a loop time)
1104 throttleSmoothingValue = std::max(static_cast<float>(-1.f), std::min(throttleSmoothingValue, static_cast<float>(1.f)));
1105 throttle = throttleSmoothingValue;
1106 }
1107
1108 if (circleCorrection) {
1109 const float roll_limited = std::max(static_cast<float>(-M_PI_4), std::min(roll, static_cast<float>(M_PI_4)));
1110 const float pitch_limited = std::max(static_cast<float>(-M_PI_4), std::min(pitch, static_cast<float>(M_PI_4)));
1111 const float yaw_limited = std::max(static_cast<float>(-M_PI_4), std::min(yaw, static_cast<float>(M_PI_4)));
1112 const float throttle_limited = std::max(static_cast<float>(-M_PI_4), std::min(throttle, static_cast<float>(M_PI_4)));
1113
1114 // Map from unit circle to linear range and limit
1115 roll = std::max(-1.0f, std::min(tanf(asinf(roll_limited)), 1.0f));
1116 pitch = std::max(-1.0f, std::min(tanf(asinf(pitch_limited)), 1.0f));
1117 yaw = std::max(-1.0f, std::min(tanf(asinf(yaw_limited)), 1.0f));
1118 throttle = std::max(-1.0f, std::min(tanf(asinf(throttle_limited)), 1.0f));
1119 }
1120
1121 if ( exponentialPercent > 0.0 ) {
1122 const float exponential = -static_cast<float>(exponentialPercent / 100.0);
1123 roll = -exponential * powf(roll, 3) + ((1 + exponential) * roll);
1124 pitch = -exponential * powf(pitch,3) + ((1 + exponential) * pitch);
1125 yaw = -exponential * powf(yaw, 3) + ((1 + exponential) * yaw);
1126 }
1127
1128 // Adjust throttle to 0:1 range
1129 if (throttleModeCenterZero && vehicle->supports()->throttleModeCenterZero()) {
1130 if (!vehicle->supports()->negativeThrust() || !negativeThrust) {
1131 throttle = std::max(0.0f, throttle);
1132 }
1133 } else {
1134 throttle = (throttle + 1.0f) / 2.0f;
1135 }
1136
1137 if (additionalAxesFunctionIsManualControl) {
1138 qCDebug(JoystickVerboseLog)
1139 << name()
1140 << "roll:" << roll
1141 << "pitch:" << -pitch
1142 << "yaw:" << yaw
1143 << "throttle:" << throttle
1144 << "pitchExtension:" << pitchExtension
1145 << "rollExtension:" << rollExtension
1146 << "additionalAxesFunction: MANUAL_CONTROL"
1147 << "aux1:" << auxManualControl1
1148 << "aux2:" << auxManualControl2
1149 << "aux3:" << auxManualControl3
1150 << "aux4:" << auxManualControl4
1151 << "aux5:" << auxManualControl5
1152 << "aux6:" << auxManualControl6;
1153 } else {
1154 qCDebug(JoystickVerboseLog)
1155 << name()
1156 << "roll:" << roll
1157 << "pitch:" << -pitch
1158 << "yaw:" << yaw
1159 << "throttle:" << throttle
1160 << "pitchExtension:" << pitchExtension
1161 << "rollExtension:" << rollExtension
1162 << "additionalAxesFunction: RC_CHANNELS_OVERRIDE"
1163 << "rcOverridePwm[0]:" << auxRcOverridePwm[0] << "enabled:" << auxRcOverrideEnabled[0]
1164 << "rcOverridePwm[1]:" << auxRcOverridePwm[1] << "enabled:" << auxRcOverrideEnabled[1]
1165 << "rcOverridePwm[2]:" << auxRcOverridePwm[2] << "enabled:" << auxRcOverrideEnabled[2]
1166 << "rcOverridePwm[3]:" << auxRcOverridePwm[3] << "enabled:" << auxRcOverrideEnabled[3]
1167 << "rcOverridePwm[4]:" << auxRcOverridePwm[4] << "enabled:" << auxRcOverrideEnabled[4]
1168 << "rcOverridePwm[5]:" << auxRcOverridePwm[5] << "enabled:" << auxRcOverrideEnabled[5];
1169 }
1170
1171 // NOTE: The buttonPressedBits going to MANUAL_CONTROL are currently used by ArduSub (and it only handles 16 bits)
1172 // Set up button bitmap
1173 quint64 buttonPressedBits = 0;
1174 for (int buttonIndex = 0; buttonIndex < _totalButtonCount; buttonIndex++) {
1175 const quint64 buttonBit = static_cast<quint64>(1LL << buttonIndex);
1176 if (_buttonEventStates[buttonIndex] == ButtonEventDownTransition || _buttonEventStates[buttonIndex] == ButtonEventRepeat) {
1177 buttonPressedBits |= buttonBit;
1178 }
1179 }
1180
1181 emit axisValues(roll, pitch, yaw, throttle);
1182
1183 const uint16_t lowButtons = static_cast<uint16_t>(buttonPressedBits & 0xFFFF);
1184 const uint16_t highButtons = static_cast<uint16_t>((buttonPressedBits >> 16) & 0xFFFF);
1185
1186
1187 vehicle->sendJoystickDataThreadSafe(roll, pitch, yaw, throttle, lowButtons, highButtons, pitchExtension, rollExtension, auxManualControl1, auxManualControl2, auxManualControl3, auxManualControl4, auxManualControl5, auxManualControl6);
1188 vehicle->sendJoystickAuxRcOverrideThreadSafe(auxRcOverridePwm, auxRcOverrideEnabled, !additionalAxesFunctionIsManualControl);
1189 }
1190}
1191
1192void Joystick::_startPollingForActiveVehicle()
1193{
1195 if (!activeVehicle) {
1196 qCWarning(JoystickLog) << "Internal Error: No active vehicle to poll for";
1197 return;
1198 }
1199 _startPollingForVehicle(*activeVehicle);
1200}
1201
1202void Joystick::_startPollingForVehicle(Vehicle &vehicle)
1203{
1204 qCDebug(JoystickLog) << "Starting joystick polling for vehicle. Vehicle id:" << vehicle.id() << "Current flags:" << _pollingFlagsToString(_pollingFlags);
1205
1206 if (_pollingFlags.testFlag(PollingForVehicle)) {
1207 qCWarning(JoystickLog) << "Internal Error: Joystick already polling for vehicle!";
1208 return;
1209 }
1210
1211 _pollingVehicle = &vehicle;
1212 qCDebug(JoystickLog) << "Started joystick polling for vehicle. Vehicle id:" << _pollingVehicle->id();
1213
1214 _buildAvailableButtonsActionList(_pollingVehicle);
1215
1216 (void) connect(this, &Joystick::setArmed, _pollingVehicle, &Vehicle::setArmedShowError);
1217 (void) connect(this, &Joystick::setVtolInFwdFlight, _pollingVehicle, &Vehicle::setVtolInFwdFlight);
1218 (void) connect(this, &Joystick::setFlightMode, _pollingVehicle, &Vehicle::setFlightMode);
1219 (void) connect(this, &Joystick::emergencyStop, _pollingVehicle, &Vehicle::emergencyStop);
1220 (void) connect(this, &Joystick::gripperAction, _pollingVehicle, &Vehicle::sendGripperAction);
1221 (void) connect(this, &Joystick::landingGearDeploy, _pollingVehicle, &Vehicle::landingGearDeploy);
1222 (void) connect(this, &Joystick::landingGearRetract, _pollingVehicle, &Vehicle::landingGearRetract);
1223 (void) connect(this, &Joystick::motorInterlock, _pollingVehicle, &Vehicle::motorInterlock);
1224
1225 (void) connect(_pollingVehicle, &Vehicle::flightModesChanged, this, &Joystick::_flightModesChanged);
1226
1227 if (GimbalController *const gimbal = _pollingVehicle->gimbalController()) {
1228 (void) connect(this, &Joystick::gimbalYawLock, gimbal, &GimbalController::gimbalYawLock);
1229 (void) connect(this, &Joystick::centerGimbal, gimbal, &GimbalController::centerGimbal);
1230 (void) connect(this, &Joystick::gimbalPitchStart, gimbal, &GimbalController::gimbalPitchStart);
1231 (void) connect(this, &Joystick::gimbalYawStart, gimbal, &GimbalController::gimbalYawStart);
1232 (void) connect(this, &Joystick::gimbalPitchStop, gimbal, &GimbalController::gimbalPitchStop);
1233 (void) connect(this, &Joystick::gimbalYawStop, gimbal, &GimbalController::gimbalYawStop);
1234 }
1235
1236 _pollingFlags |= PollingForVehicle;
1237 _startPollingThread();
1238}
1239
1240void Joystick::_startPollingForConfiguration()
1241{
1242 qCDebug(JoystickLog) << "Starting joystick polling for configuration. Current flags:" << _pollingFlagsToString(_pollingFlags);
1243
1244 if (_pollingFlags.testFlag(PollingForConfiguration)) {
1245 qCWarning(JoystickLog) << "Internal Error: Joystick already polling for configuration!";
1246 return;
1247 }
1248
1249 _pollingFlags |= PollingForConfiguration;
1250 _startPollingThread();
1251}
1252
1253void Joystick::_stopPollingForConfiguration()
1254{
1255 qCDebug(JoystickLog) << "Stopping joystick polling for configuration. Current flags:" << _pollingFlagsToString(_pollingFlags);
1256
1257 if (!_pollingFlags.testFlag(PollingForConfiguration)) {
1258 qCWarning(JoystickLog) << "Internal Error: Joystick not polling for configuration!";
1259 return;
1260 }
1261
1262 if (!isRunning()) {
1263 qCWarning(JoystickLog) << "Internal Error: Joystick polling thread not running!";
1264 }
1265
1266 const PollingFlags remainingFlags = _pollingFlags & ~PollingFlags(PollingForConfiguration);
1267
1268 // Stop the thread BEFORE updating _pollingFlags. If we cleared the flags first and the
1269 // thread was still running, _handleButtons()/_handleAxis() would see PollingNone and
1270 // fire a spurious "Internal Error: Joystick not polling!" warning.
1271 if (remainingFlags == PollingNone) {
1272 _stopPollingThread();
1273 }
1274
1275 _pollingFlags = remainingFlags;
1276 qCDebug(JoystickLog) << "Remaining flags:" << _pollingFlagsToString(_pollingFlags);
1277
1278 if (remainingFlags.testFlag(PollingForVehicle) && !isRunning()) {
1279 qCWarning(JoystickLog) << "Internal Error: Joystick polling not running! Forcing start of polling thread. Continuing polling for vehicle.";
1280 _startPollingThread();
1281 }
1282}
1283
1284void Joystick::_stopAllPollingForVehicle()
1285{
1286 qCDebug(JoystickLog) << "Stopping all joystick polling for vehicle. Current flags:" << _pollingFlagsToString(_pollingFlags);
1287
1288 if (_pollingVehicle) {
1289 _pollingVehicle->sendJoystickAuxRcOverrideThreadSafe({}, {}, false);
1290 (void) disconnect(this, nullptr, _pollingVehicle, nullptr);
1291 (void) disconnect(_pollingVehicle, &Vehicle::flightModesChanged, this, &Joystick::_flightModesChanged);
1292 if (GimbalController *const gimbal = _pollingVehicle->gimbalController()) {
1293 (void) disconnect(this, nullptr, gimbal, nullptr);
1294 }
1295 if (!isRunning()) {
1296 qCWarning(JoystickLog) << "Joystick polling thread not running even though _pollingVehicle was set.";
1297 }
1298 }
1299
1300 const PollingFlags remainingFlags = _pollingFlags & ~PollingFlags(PollingForVehicle);
1301
1302 // Stop the thread BEFORE updating _pollingFlags. If we cleared the flags first and the
1303 // thread was still running, _handleButtons()/_handleAxis() would see PollingNone and
1304 // fire a spurious "Internal Error: Joystick not polling!" warning.
1305 if (remainingFlags == PollingNone) {
1306 _stopPollingThread();
1307 }
1308
1309 // Clear _pollingVehicle AFTER updating _pollingFlags so the polling thread never sees
1310 // PollingForVehicle set with a null _pollingVehicle, which would fire spurious
1311 // "Internal Error: No vehicle for joystick!" warnings.
1312 _pollingVehicle = nullptr;
1313 _pollingFlags = remainingFlags;
1314 qCDebug(JoystickLog) << "Remaining flags:" << _pollingFlagsToString(_pollingFlags);
1315
1316 if (remainingFlags.testFlag(PollingForConfiguration) && !isRunning()) {
1317 qCWarning(JoystickLog) << "Joystick polling thread not running but configuration polling flag set. Forcing start.";
1318 _startPollingThread();
1319 }
1320}
1321
1322void Joystick::_startPollingThread()
1323{
1324 if (isRunning()) {
1325 qCDebug(JoystickLog) << "Polling thread already running. Flags:" << _pollingFlagsToString(_pollingFlags);
1326 } else {
1327 qCDebug(JoystickLog) << "Starting polling thread. Flags:" << _pollingFlagsToString(_pollingFlags);
1328 _exitPollingThread = false;
1329 start();
1330 }
1331}
1332
1333void Joystick::_stopPollingThread()
1334{
1335 if (isRunning()) {
1336 qCDebug(JoystickLog) << "Stopping polling thread. Flags:" << _pollingFlagsToString(_pollingFlags);
1337 _exitPollingThread = true;
1338 if (QThread::currentThread() == this) {
1339 qCWarning(JoystickLog) << "Skipping wait() on joystick thread to avoid deadlock";
1340 } else {
1341 wait();
1342 }
1343 // _exitPollingThread is reset to false in _startPollingThread() before the next start()
1344 } else {
1345 qCDebug(JoystickLog) << "Polling thread already stopped. Flags:" << _pollingFlagsToString(_pollingFlags);
1346 }
1347}
1348
1349QString Joystick::_pollingFlagsToString(PollingFlags flags) const
1350{
1351 if (flags == PollingNone) {
1352 return QStringLiteral("None");
1353 }
1354 QStringList parts;
1355 if (flags.testFlag(PollingForVehicle)) {
1356 parts << QStringLiteral("PollingForVehicle");
1357 }
1358 if (flags.testFlag(PollingForConfiguration)) {
1359 parts << QStringLiteral("PollingForConfiguration");
1360 }
1361 return parts.join(QStringLiteral("|"));
1362}
1363
1364void Joystick::setAxisCalibration(int axis, const AxisCalibration_t &calibration)
1365{
1366 if (!_validAxis(axis)) {
1367 return;
1368 }
1369
1370 _rgCalibration[axis] = calibration;
1371}
1372
1374{
1375 if (!_validAxis(axis)) {
1376 return AxisCalibration_t{};
1377 }
1378
1379 return _rgCalibration[axis];
1380}
1381
1382void Joystick::_setJoystickAxisForAxisFunction(AxisFunction_t axisFunction, int joystickAxis)
1383{
1384 if (axisFunction == maxAxisFunction) {
1385 qCWarning(JoystickLog) << "Internal Error: maxAxisFunction passed to _setJoystickAxisForAxisFunction";
1386 return;
1387 }
1388 if (joystickAxis == kJoystickAxisNotAssigned) {
1389 qCWarning(JoystickLog) << "Internal Error: kJoystickAxisNotAssigned passed to _setJoystickAxisForAxisFunction";
1390 return;
1391 }
1392
1393 if (!_validAxis(joystickAxis)) {
1394 return;
1395 }
1396
1397 _axisFunctionToJoystickAxisMap[axisFunction] = joystickAxis;
1398}
1399
1400int Joystick::_getJoystickAxisForAxisFunction(AxisFunction_t axisFunction) const
1401{
1402 if (axisFunction == maxAxisFunction) {
1403 qCWarning(JoystickLog) << "Internal Error: maxAxisFunction passed to _getJoystickAxisForAxisFunction";
1404 return kJoystickAxisNotAssigned;
1405 }
1406
1407 return _axisFunctionToJoystickAxisMap.value(axisFunction, kJoystickAxisNotAssigned);
1408}
1409
1411{
1412 switch (axisFunction) {
1413 case rollFunction:
1415 case pitchFunction:
1417 case yawFunction:
1419 case throttleFunction:
1437 case maxAxisFunction:
1439 }
1440 Q_UNREACHABLE();
1441}
1442
1444{
1445 switch (stickFunction) {
1447 return rollFunction;
1449 return pitchFunction;
1451 return yawFunction;
1453 return throttleFunction;
1469 return rollExtensionFunction;
1471 return maxAxisFunction;
1472 }
1473 Q_UNREACHABLE();
1474}
1475
1477{
1478 _setJoystickAxisForAxisFunction(mapRCCStickFunctionToAxisFunction(stickFunction), channel);
1479}
1480
1482{
1483 return _getJoystickAxisForAxisFunction(mapRCCStickFunctionToAxisFunction(stickFunction));
1484}
1485
1486void Joystick::setButtonRepeat(int button, bool repeat)
1487{
1488 if (!_validButton(button) || !_assignedButtonActions[button]) {
1489 return;
1490 }
1491
1492 _assignedButtonActions[button]->repeat = repeat;
1493 _assignedButtonActions[button]->buttonElapsedTimer.start();
1494
1495 QSettings settings;
1496 settings.beginGroup(_joystickSettings.settingsGroup());
1497 settings.beginGroup(QString::fromLatin1(kButtonActionArrayGroup));
1498 settings.beginGroup(QString::number(button));
1499 const QString repeatKey = QString::fromLatin1(kButtonRepeatKey);
1500 settings.setValue(repeatKey, repeat);
1501 settings.endGroup();
1502 settings.endGroup();
1503 settings.endGroup();
1504}
1505
1507{
1508 if (!_validButton(button) || !_assignedButtonActions[button]) {
1509 return false;
1510 }
1511
1512 return _assignedButtonActions[button]->repeat;
1513}
1514
1515void Joystick::setButtonAction(int button, const QString& actionName)
1516{
1517 if (!_validButton(button)) {
1518 return;
1519 }
1520
1521 qCDebug(JoystickLog) << "button:actionName" << button << actionName;
1522
1523 QSettings settings;
1524 settings.beginGroup(_joystickSettings.settingsGroup());
1525 settings.beginGroup(QString::fromLatin1(kButtonActionArrayGroup));
1526 settings.beginGroup(QString::number(button));
1527 const QString actionNameKey = QString::fromLatin1(kButtonActionNameKey);
1528 const QString repeatKey = QString::fromLatin1(kButtonRepeatKey);
1529
1530 if (actionName.isEmpty() || actionName == _buttonActionNone) {
1531 if (_assignedButtonActions[button]) {
1532 delete _assignedButtonActions[button];
1533 _assignedButtonActions[button] = nullptr;
1534 }
1535 settings.setValue(actionNameKey, _buttonActionNone);
1536 settings.setValue(repeatKey, false);
1537 } else {
1538 if (!_assignedButtonActions[button]) {
1539 _assignedButtonActions[button] = new AssignedButtonAction(actionName, false /* repeat */);
1540 } else {
1541 _assignedButtonActions[button]->actionName = actionName;
1542 //-- Make sure repeat is off if this action doesn't support repeats
1543 const int idx = _findAvailableButtonActionIndex(actionName);
1544 if (idx >= 0) {
1545 const AvailableButtonAction *const buttonAction = qobject_cast<const AvailableButtonAction*>(_availableButtonActions->get(idx));
1546 if (!buttonAction->canRepeat()) {
1547 _assignedButtonActions[button]->repeat = false;
1548 }
1549 }
1550 }
1551
1552 settings.setValue(actionNameKey, _assignedButtonActions[button]->actionName);
1553 settings.setValue(repeatKey, _assignedButtonActions[button]->repeat);
1554 }
1555
1556 settings.endGroup();
1557 settings.endGroup();
1558 settings.endGroup();
1559
1560 emit buttonActionsChanged();
1561}
1562
1563QString Joystick::getButtonAction(int button) const
1564{
1565 if (_validButton(button)) {
1566 if (_assignedButtonActions[button]) {
1567 return _assignedButtonActions[button]->actionName;
1568 }
1569 }
1570
1571 return QString(_buttonActionNone);
1572}
1573
1574QStringList Joystick::buttonActions() const
1575{
1576 QStringList list;
1577
1578 list.reserve(_totalButtonCount);
1579
1580 for (int button = 0; button < _totalButtonCount; button++) {
1581 if (_assignedButtonActions[button]) {
1582 list << _assignedButtonActions[button]->actionName;
1583 } else {
1584 list << _buttonActionNone;
1585 }
1586 }
1587
1588 return list;
1589}
1590
1591void Joystick::_executeButtonAction(const QString &action, const ButtonEvent_t buttonEvent)
1592{
1593 Vehicle *const vehicle = _pollingVehicle;
1594 if (!vehicle) {
1595 qCWarning(JoystickLog) << "Internal Error: No vehicle for joystick!";
1596 return;
1597 }
1598 if (!_joystickManager->activeJoystickEnabledForActiveVehicle()) {
1599 qCWarning(JoystickLog) << "Internal Error: Joystick not enabled for vehicle!";
1600 return;
1601 }
1602 if (action == _buttonActionNone) {
1603 return;
1604 }
1605
1606 const int idx = _findAvailableButtonActionIndex(action);
1607 if (idx >= 0) {
1608 const AvailableButtonAction *const availAction = qobject_cast<const AvailableButtonAction*>(_availableButtonActions->get(idx));
1609 std::function<void()> handler;
1610 switch (buttonEvent) {
1612 handler = availAction->onDown();
1613 break;
1614 case ButtonEventRepeat:
1615 handler = availAction->onRepeat();
1616 break;
1618 handler = availAction->onUp();
1619 break;
1620 default:
1621 return;
1622 }
1623 if (handler) {
1624 qCDebug(JoystickLog) << "Button Action:" << action << buttonEvent;
1625 handler();
1626 return;
1627 }
1628 // No lambda for this event — only DownTransition falls through to flight mode / MAVLink handling
1629 if (buttonEvent != ButtonEventDownTransition) {
1630 return;
1631 }
1632 }
1633
1634 if (buttonEvent != ButtonEventDownTransition) {
1635 return;
1636 }
1637
1638 // Flight mode check
1639 if (vehicle->flightModes().contains(action)) {
1640 qCDebug(JoystickLog) << "Button Action: Switching flight mode to" << action;
1641 emit setFlightMode(action);
1642 return;
1643 }
1644
1645 // MAVLink actions
1646 emit unknownAction(action);
1647 for (int i = 0; i < _mavlinkActionManager->actions()->count(); i++) {
1648 MavlinkAction *const mavlinkAction = _mavlinkActionManager->actions()->value<MavlinkAction*>(i);
1649 if (action == mavlinkAction->label()) {
1650 qCDebug(JoystickLog) << "Button Action: Sending MAVLink action" << action;
1651 mavlinkAction->sendTo(vehicle);
1652 return;
1653 }
1654 }
1655}
1656
1657bool Joystick::_validAxis(int axis) const
1658{
1659 if ((axis >= 0) && (axis < _axisCount)) {
1660 return true;
1661 }
1662
1663 qCWarning(JoystickLog) << "Invalid axis index" << axis;
1664 return false;
1665}
1666
1667bool Joystick::_validButton(int button) const
1668{
1669 if ((button >= 0) && (button < _totalButtonCount)) {
1670 return true;
1671 }
1672
1673 qCWarning(JoystickLog) << "Invalid button index" << button;
1674 return false;
1675}
1676
1677int Joystick::_findAvailableButtonActionIndex(const QString &action)
1678{
1679 for (int i = 0; i < _availableButtonActions->count(); i++) {
1680 const AvailableButtonAction *const buttonAction = qobject_cast<const AvailableButtonAction*>(_availableButtonActions->get(i));
1681 if (buttonAction->action() == action) {
1682 return i;
1683 }
1684 }
1685
1686 return -1;
1687}
1688
1689void Joystick::_addAvailableButtonActionIfMissing(const QString &action)
1690{
1691 if (_findAvailableButtonActionIndex(action) != -1) {
1692 return;
1693 }
1694
1695 // No onDown handler: _executeButtonAction() falls through to flight mode / MAVLink
1696 // dispatch at button press time.
1697 _availableButtonActions->append(new AvailableButtonAction(action, nullptr));
1698 _availableActionTitles << action;
1700}
1701
1702void Joystick::_buildAvailableButtonsActionList(Vehicle *vehicle)
1703{
1704 if (_availableButtonActions->count()) {
1705 _availableButtonActions->clearAndDeleteContents();
1706 }
1707 _availableActionTitles.clear();
1708
1709 _availableButtonActions->append(new AvailableButtonAction(_buttonActionNone, nullptr));
1710 _availableButtonActions->append(new AvailableButtonAction(_buttonActionArm,
1711 [this]() { emit setArmed(true); }));
1712 _availableButtonActions->append(new AvailableButtonAction(_buttonActionDisarm,
1713 [this]() { emit setArmed(false); }));
1714 _availableButtonActions->append(new AvailableButtonAction(_buttonActionToggleArm,
1715 [this]() { emit setArmed(!_pollingVehicle->armed()); }));
1716 if (vehicle) {
1717 const QStringList list = vehicle->flightModes();
1718 for (const QString &mode : list) {
1719 _availableButtonActions->append(new AvailableButtonAction(mode, nullptr));
1720 }
1721 }
1722
1723 _availableButtonActions->append(new AvailableButtonAction(_buttonActionVTOLFixedWing,
1724 [this]() { emit setVtolInFwdFlight(true); }));
1725 _availableButtonActions->append(new AvailableButtonAction(_buttonActionVTOLMultiRotor,
1726 [this]() { emit setVtolInFwdFlight(false); }));
1727 _availableButtonActions->append(new AvailableButtonAction(_buttonActionContinuousZoomIn,
1728 [this]() { emit startContinuousZoom(1); },
1729 [this]() { emit stopContinuousZoom(); }));
1730 _availableButtonActions->append(new AvailableButtonAction(_buttonActionContinuousZoomOut,
1731 [this]() { emit startContinuousZoom(-1); },
1732 [this]() { emit stopContinuousZoom(); }));
1733 _availableButtonActions->append(new AvailableButtonAction(_buttonActionStepZoomIn,
1734 [this]() { emit stepZoom(1); },
1735 nullptr,
1736 [this]() { emit stepZoom(1); }));
1737 _availableButtonActions->append(new AvailableButtonAction(_buttonActionStepZoomOut,
1738 [this]() { emit stepZoom(-1); },
1739 nullptr,
1740 [this]() { emit stepZoom(-1); }));
1741 _availableButtonActions->append(new AvailableButtonAction(_buttonActionContinuousFocusIn,
1742 [this]() { emit startContinuousFocus(1); },
1743 [this]() { emit stopContinuousFocus(); }));
1744 _availableButtonActions->append(new AvailableButtonAction(_buttonActionContinuousFocusOut,
1745 [this]() { emit startContinuousFocus(-1); },
1746 [this]() { emit stopContinuousFocus(); }));
1747 _availableButtonActions->append(new AvailableButtonAction(_buttonActionStepFocusIn,
1748 [this]() { emit stepFocus(1); },
1749 nullptr,
1750 [this]() { emit stepFocus(1); }));
1751 _availableButtonActions->append(new AvailableButtonAction(_buttonActionStepFocusOut,
1752 [this]() { emit stepFocus(-1); },
1753 nullptr,
1754 [this]() { emit stepFocus(-1); }));
1755 _availableButtonActions->append(new AvailableButtonAction(_buttonActionNextStream,
1756 [this]() { emit stepStream(1); }));
1757 _availableButtonActions->append(new AvailableButtonAction(_buttonActionPreviousStream,
1758 [this]() { emit stepStream(-1); }));
1759 _availableButtonActions->append(new AvailableButtonAction(_buttonActionNextCamera,
1760 [this]() { emit stepCamera(1); }));
1761 _availableButtonActions->append(new AvailableButtonAction(_buttonActionPreviousCamera,
1762 [this]() { emit stepCamera(-1); }));
1763 _availableButtonActions->append(new AvailableButtonAction(_buttonActionTriggerCamera,
1764 [this]() { emit triggerCamera(); }));
1765 _availableButtonActions->append(new AvailableButtonAction(_buttonActionStartVideoRecord,
1766 [this]() { emit startVideoRecord(); }));
1767 _availableButtonActions->append(new AvailableButtonAction(_buttonActionStopVideoRecord,
1768 [this]() { emit stopVideoRecord(); }));
1769 _availableButtonActions->append(new AvailableButtonAction(_buttonActionToggleVideoRecord,
1770 [this]() { emit toggleVideoRecord(); }));
1771 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGimbalDown,
1772 [this]() { emit gimbalPitchStart(-1); },
1773 [this]() { emit gimbalPitchStop(); }));
1774 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGimbalUp,
1775 [this]() { emit gimbalPitchStart(1); },
1776 [this]() { emit gimbalPitchStop(); }));
1777 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGimbalLeft,
1778 [this]() { emit gimbalYawStart(-1); },
1779 [this]() { emit gimbalYawStop(); }));
1780 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGimbalRight,
1781 [this]() { emit gimbalYawStart(1); },
1782 [this]() { emit gimbalYawStop(); }));
1783 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGimbalCenter,
1784 [this]() { emit centerGimbal(); }));
1785 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGimbalYawLock,
1786 [this]() { emit gimbalYawLock(true); }));
1787 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGimbalYawFollow,
1788 [this]() { emit gimbalYawLock(false); }));
1789 _availableButtonActions->append(new AvailableButtonAction(_buttonActionEmergencyStop,
1790 [this]() { emit emergencyStop(); }));
1791 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGripperGrab,
1792 [this]() { emit gripperAction(GRIPPER_ACTION_GRAB); }));
1793 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGripperRelease,
1794 [this]() { emit gripperAction(GRIPPER_ACTION_RELEASE); }));
1795 _availableButtonActions->append(new AvailableButtonAction(_buttonActionGripperHold,
1796 [this]() { emit gripperAction(GRIPPER_ACTION_HOLD); }));
1797 _availableButtonActions->append(new AvailableButtonAction(_buttonActionLandingGearDeploy,
1798 [this]() { emit landingGearDeploy(); }));
1799 _availableButtonActions->append(new AvailableButtonAction(_buttonActionLandingGearRetract,
1800 [this]() { emit landingGearRetract(); }));
1801 _availableButtonActions->append(new AvailableButtonAction(_buttonActionMotorInterlockEnable,
1802 [this]() { emit motorInterlock(true); }));
1803 _availableButtonActions->append(new AvailableButtonAction(_buttonActionMotorInterlockDisable,
1804 [this]() { emit motorInterlock(false); }));
1805
1806 const auto customActions = QGCCorePlugin::instance()->joystickActions();
1807 for (const auto &action : customActions) {
1808 // onDown is nullptr — dispatch falls through to unknownAction (DownTransition only).
1809 // A non-null onRepeat makes canRepeat() return true, preserving the UI toggle,
1810 // but custom plugin repeat dispatch is not supported (unknownAction carries no event type).
1811 std::function<void()> repeatFn = action.canRepeat ? std::function<void()>([]{}) : nullptr;
1812 _availableButtonActions->append(new AvailableButtonAction(action.name, nullptr, nullptr, repeatFn));
1813 }
1814
1815 for (int i = 0; i < _mavlinkActionManager->actions()->count(); i++) {
1816 const MavlinkAction *const mavlinkAction = _mavlinkActionManager->actions()->value<const MavlinkAction*>(i);
1817 _availableButtonActions->append(new AvailableButtonAction(mavlinkAction->label(), nullptr));
1818 }
1819
1820 // Always include actions currently assigned to buttons even if they are not otherwise
1821 // available right now. Flight modes can change dynamically (standard modes protocol),
1822 // so an assignment stored in settings may reference a mode which only shows up later.
1823 // Keeping them in the list means the UI combo always displays the real assignment.
1824 for (int buttonIndex = 0; buttonIndex < _assignedButtonActions.count(); buttonIndex++) {
1825 const AssignedButtonAction *const assigned = _assignedButtonActions[buttonIndex];
1826 if (assigned && (_findAvailableButtonActionIndex(assigned->actionName) == -1)) {
1827 _availableButtonActions->append(new AvailableButtonAction(assigned->actionName, nullptr));
1828 }
1829 }
1830
1831 for (int i = 0; i < _availableButtonActions->count(); i++) {
1832 const AvailableButtonAction *const buttonAction = qobject_cast<const AvailableButtonAction*>(_availableButtonActions->get(i));
1833 _availableActionTitles << buttonAction->action();
1834 }
1835
1837}
1838
1840{
1841 switch (function) {
1842 case rollFunction:
1843 return QStringLiteral("Roll");
1844 case pitchFunction:
1845 return QStringLiteral("Pitch");
1846 case yawFunction:
1847 return QStringLiteral("Yaw");
1848 case throttleFunction:
1849 return QStringLiteral("Throttle");
1851 return QStringLiteral("Additional Axis 1");
1853 return QStringLiteral("Additional Axis 2");
1855 return QStringLiteral("Additional Axis 3");
1857 return QStringLiteral("Additional Axis 4");
1859 return QStringLiteral("Additional Axis 5");
1861 return QStringLiteral("Additional Axis 6");
1863 return QStringLiteral("Pitch Extension");
1865 return QStringLiteral("Roll Extension");
1866 case maxAxisFunction:
1867 return QStringLiteral("Unassigned");
1868 }
1869 Q_UNREACHABLE();
1870}
1871
1873{
1874 _startPollingForConfiguration();
1875}
1876
1878{
1879 _stopPollingForConfiguration();
1880}
1881
1882Joystick::AxisFunction_t Joystick::_getAxisFunctionForJoystickAxis(int joystickAxis) const
1883{
1884 if (!_validAxis(joystickAxis)) {
1885 return maxAxisFunction;
1886 }
1887
1888 for (int i = 0; i < maxAxisFunction; i++) {
1889 auto axisFunction = static_cast<AxisFunction_t>(i);
1890 if (_axisFunctionToJoystickAxisMap.value(axisFunction, kJoystickAxisNotAssigned) == joystickAxis) {
1891 return axisFunction;
1892 }
1893 }
1894
1895 return maxAxisFunction;
1896}
1897
1899{
1900 _exitPollingThread = true;
1901 if (isRunning()) {
1902 if (QThread::currentThread() == this) {
1903 qCWarning(JoystickLog) << "Skipping wait() on joystick thread";
1904 } else {
1905 wait();
1906 }
1907 }
1908}
1909
1910void Joystick::setLinkedGroupId(const QString &groupId)
1911{
1912 if (_linkedGroupId != groupId) {
1913 _linkedGroupId = groupId;
1914
1915 // Persist to settings
1916 QSettings settings;
1917 settings.beginGroup(QStringLiteral("Joystick"));
1918 settings.beginGroup(_name);
1919 if (groupId.isEmpty()) {
1920 settings.remove(QStringLiteral("LinkedGroupId"));
1921 } else {
1922 settings.setValue(QStringLiteral("LinkedGroupId"), groupId);
1923 }
1924 settings.endGroup();
1925 settings.endGroup();
1926
1927 emit linkedGroupChanged();
1928 }
1929}
1930
1931void Joystick::setLinkedGroupRole(const QString &role)
1932{
1933 if (_linkedGroupRole != role) {
1934 _linkedGroupRole = role;
1935
1936 // Persist to settings
1937 QSettings settings;
1938 settings.beginGroup(QStringLiteral("Joystick"));
1939 settings.beginGroup(_name);
1940 if (role.isEmpty()) {
1941 settings.remove(QStringLiteral("LinkedGroupRole"));
1942 } else {
1943 settings.setValue(QStringLiteral("LinkedGroupRole"), role);
1944 }
1945 settings.endGroup();
1946 settings.endGroup();
1947
1948 emit linkedGroupChanged();
1949 }
1950}
#define QGC_LOGGING_CATEGORY(name, categoryStr)
AssignedButtonAction(const QString &name, bool repeat)
Definition Joystick.cc:54
const std::function< void()> & onUp() const
Definition Joystick.h:50
const QString & action() const
Definition Joystick.h:46
const std::function< void()> & onDown() const
Definition Joystick.h:48
const std::function< void()> & onRepeat() const
Definition Joystick.h:49
AvailableButtonAction(const QString &actionName, std::function< void()> onDown, std::function< void()> onUp=nullptr, std::function< void()> onRepeat=nullptr, QObject *parent=nullptr)
Definition Joystick.cc:61
bool canRepeat() const
Definition Joystick.h:47
A Fact is used to hold a single value within the system.
Definition Fact.h:17
void rawValueChanged(const QVariant &value)
Q_INVOKABLE void centerGimbal()
void gimbalYawLock(bool yawLock)
void gimbalYawStart(int direction)
void gimbalPitchStart(int direction)
bool activeJoystickEnabledForActiveVehicle() const
static JoystickManager * instance()
void setActiveJoystickEnabledForActiveVehicle(bool enabled)
void setLinkedGroupId(const QString &groupId)
Definition Joystick.cc:1910
Q_INVOKABLE void stopConfiguration()
Tells the joystick that the configuration UI is being closed so it can do any special processing requ...
Definition Joystick.cc:1877
void rawButtonPressedChanged(int index, bool pressed)
Signalled during PollingForConfiguration.
void setArmed(bool arm)
void gimbalYawLock(bool lock)
int _buttonCount
Definition Joystick.h:388
void triggerCamera()
virtual ~Joystick()
Definition Joystick.cc:165
@ pitchExtensionFunction
Definition Joystick.h:142
@ additionalAxis2Function
Definition Joystick.h:145
@ rollFunction
Definition Joystick.h:138
@ yawFunction
Definition Joystick.h:140
@ additionalAxis6Function
Definition Joystick.h:149
@ additionalAxis3Function
Definition Joystick.h:146
@ additionalAxis4Function
Definition Joystick.h:147
@ maxAxisFunction
Definition Joystick.h:150
@ additionalAxis1Function
Definition Joystick.h:144
@ additionalAxis5Function
Definition Joystick.h:148
@ throttleFunction
Definition Joystick.h:141
@ rollExtensionFunction
Definition Joystick.h:143
@ pitchFunction
Definition Joystick.h:139
QString name() const
Definition Joystick.h:205
Joystick(const QString &name, int axisCount, int buttonCount, int hatCount, QObject *parent=nullptr)
Definition Joystick.cc:73
void stopContinuousFocus()
RemoteControlCalibrationController::StickFunction mapAxisFunctionToRCCStickFunction(AxisFunction_t axisFunction) const
Definition Joystick.cc:1410
void startVideoRecord()
QString _name
Definition Joystick.h:386
int _hatCount
Definition Joystick.h:389
void setLinkedGroupRole(const QString &role)
Definition Joystick.cc:1931
void setFlightMode(const QString &flightMode)
void motorInterlock(bool enable)
static QString axisFunctionToString(AxisFunction_t function)
Definition Joystick.cc:1839
void setFunctionForChannel(RemoteControlCalibrationController::StickFunction stickFunction, int channel)
Definition Joystick.cc:1476
void axisValues(float roll, float pitch, float yaw, float throttle)
void landingGearDeploy()
void buttonActionsChanged()
void stop()
Definition Joystick.cc:1898
int axisCount() const
Definition Joystick.h:207
@ ButtonEventDownTransition
Definition Joystick.h:116
@ ButtonEventNone
Definition Joystick.h:118
@ ButtonEventRepeat
Definition Joystick.h:117
@ ButtonEventUpTransition
Definition Joystick.h:115
void assignableActionsChanged()
void centerGimbal()
void unknownAction(const QString &action)
int getChannelForFunction(RemoteControlCalibrationController::StickFunction stickFunction) const
Definition Joystick.cc:1481
Q_INVOKABLE QString getButtonAction(int button) const
Definition Joystick.cc:1563
void stopVideoRecord()
void stepZoom(int direction)
QStringList buttonActions() const
Definition Joystick.cc:1574
AxisFunction_t mapRCCStickFunctionToAxisFunction(RemoteControlCalibrationController::StickFunction stickFunction) const
Definition Joystick.cc:1443
Q_INVOKABLE void startConfiguration()
Tells the joystick that the configuration UI is being displayed so it can do any special processing r...
Definition Joystick.cc:1872
Joystick::AxisCalibration_t getAxisCalibration(int axis) const
Definition Joystick.cc:1373
void setAxisCalibration(int axis, const AxisCalibration_t &calibration)
Definition Joystick.cc:1364
void gimbalPitchStop()
int _axisCount
Definition Joystick.h:387
void gimbalYawStop()
void startContinuousZoom(int direction)
JoystickSettings * settings()
Definition Joystick.h:204
void stopContinuousZoom()
void emergencyStop()
Q_INVOKABLE bool getButtonRepeat(int button)
Definition Joystick.cc:1506
int buttonCount() const
Definition Joystick.h:206
Q_INVOKABLE void setButtonAction(int button, const QString &action)
Definition Joystick.cc:1515
void stepCamera(int direction)
void setVtolInFwdFlight(bool set)
void gripperAction(GRIPPER_ACTIONS gripperAction)
void stepStream(int direction)
void landingGearRetract()
void toggleVideoRecord()
void stepFocus(int direction)
void gimbalPitchStart(int direction)
void linkedGroupChanged()
void rawChannelValuesChanged(QVector< int > channelValues)
Signalled during PollingForConfiguration.
void startContinuousFocus(int direction)
void gimbalYawStart(int direction)
Q_INVOKABLE void setButtonRepeat(int button, bool repeat)
Definition Joystick.cc:1486
Loads the specified action file and provides access to the actions it contains.
QmlObjectListModel * actions()
const QString & label() const
Q_INVOKABLE void sendTo(Vehicle *vehicle)
static MultiVehicleManager * instance()
Vehicle * activeVehicle() const
virtual QList< JoystickAction > joystickActions()
static QGCCorePlugin * instance()
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
T value(int index) const
Q_INVOKABLE QObject * get(int index)
int count() const override final
void clearAndDeleteContents() override final
Clears the list and calls deleteLater on each entry.
StickFunction
These identify the various controls functions. They are also used as indices into the _rgFunctioInfo ...
QString settingsGroup() const
Provides access to all app settings.
bool negativeThrust() const
bool throttleModeCenterZero() const
Q_INVOKABLE void motorInterlock(bool enable)
Command vehicle to Enable/Disable Motor Interlock.
Definition Vehicle.cc:3386
Q_INVOKABLE void sendGripperAction(GRIPPER_ACTIONS gripperOption)
Definition Vehicle.cc:3134
void sendJoystickAuxRcOverrideThreadSafe(const std::array< uint16_t, kAuxRcOverrideChannelCount > &channelValues, const std::array< bool, kAuxRcOverrideChannelCount > &channelEnabled, bool useRcOverride)
Definition Vehicle.cc:3038
Q_INVOKABLE void emergencyStop()
Command vehicle to kill all motors no matter what state.
Definition Vehicle.cc:2075
int id() const
Definition Vehicle.h:429
void setFlightMode(const QString &flightMode)
Definition Vehicle.cc:1472
Q_INVOKABLE void landingGearDeploy()
Command vichecle to deploy landing gear.
Definition Vehicle.cc:2085
void flightModesChanged()
GimbalController * gimbalController()
Definition Vehicle.h:743
void setVtolInFwdFlight(bool vtolInFwdFlight)
Definition Vehicle.cc:2454
void sendJoystickDataThreadSafe(float roll, float pitch, float yaw, float thrust, quint16 buttons, quint16 buttons2, float pitchExtension, float rollExtension, float aux1, float aux2, float aux3, float aux4, float aux5, float aux6)
Definition Vehicle.cc:2978
bool armed() const
Definition Vehicle.h:453
Q_INVOKABLE void landingGearRetract()
Command vichecle to retract landing gear.
Definition Vehicle.cc:2095
VehicleSupports * supports()
Definition Vehicle.h:406
QStringList flightModes()
Definition Vehicle.cc:1456
void setArmedShowError(bool armed)
Definition Vehicle.h:455