地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1858 lines
75 KiB

// On Windows (for VS2010) stdint.h contains the limits normally contained in limits.h
// It also needs the __STDC_LIMIT_MACROS macro defined in order to include them (done
// in qgroundcontrol.pri).
#ifdef WIN32
#include <stdint.h>
#else
#include <limits.h>
#endif
#include <QTimer>
#include <QDir>
#include <QXmlStreamReader>
#include <QMessageBox>
#include <QLabel>
#include "QGCPX4VehicleConfig.h"
#include "QGC.h"
#include "QGCToolWidget.h"
#include "UASManager.h"
#include "LinkManager.h"
#include "UASParameterCommsMgr.h"
#include "ui_QGCPX4VehicleConfig.h"
#include "px4_configuration/QGCPX4AirframeConfig.h"
#include "px4_configuration/QGCPX4SensorCalibration.h"
#include <dialog_bare.h>
#define WIDGET_INDEX_FIRMWARE 0
#define WIDGET_INDEX_RC 1
#define WIDGET_INDEX_SENSOR_CAL 2
#define WIDGET_INDEX_AIRFRAME_CONFIG 3
#define WIDGET_INDEX_GENERAL_CONFIG 4
#define WIDGET_INDEX_ADV_CONFIG 5
#define MIN_PWM_VAL 800
#define MAX_PWM_VAL 2200
QGCPX4VehicleConfig::QGCPX4VehicleConfig(QWidget *parent) :
QWidget(parent),
mav(NULL),
chanCount(0),
channelWanted(-1),
channelReverseStateWanted(-1),
rcRoll(0.0f),
rcPitch(0.0f),
rcYaw(0.0f),
rcThrottle(0.0f),
rcMode(0.0f),
rcAssist(0.0f),
rcMission(0.0f),
rcReturn(0.0f),
rcFlaps(0.0f),
rcAux1(0.0f),
rcAux2(0.0f),
dataModelChanged(true),
calibrationEnabled(false),
configEnabled(false),
px4AirframeConfig(NULL),
#ifdef QUPGRADE_SUPPORT
firmwareDialog(NULL),
#endif
planeBack(":/files/images/px4/rc/cessna_back.png"),
planeSide(":/files/images/px4/rc/cessna_side.png"),
px4SensorCalibration(NULL),
ui(new Ui::QGCPX4VehicleConfig)
{
doneLoadingConfig = false;
channelNames << "Roll / Aileron";
channelNames << "Pitch / Elevator";
channelNames << "Yaw / Rudder";
channelNames << "Throttle";
channelNames << "Main Mode Switch";
channelNames << "Assist Switch";
channelNames << "Mission Switch";
channelNames << "Return Switch";
channelNames << "Flaps";
channelNames << "Aux1";
channelNames << "Aux2";
channelNames << "Aux3";
channelNames << "Aux4";
channelNames << "Aux5";
channelNames << "Aux6";
channelNames << "Aux7";
channelNames << "Aux8";
setObjectName("QGC_VEHICLECONFIG");
ui->setupUi(this);
ui->advancedMenuButton->setEnabled(false);
ui->airframeMenuButton->setEnabled(false);
ui->sensorMenuButton->setEnabled(false);
ui->rcMenuButton->setEnabled(false);
ui->generalMenuButton->hide();
px4AirframeConfig = new QGCPX4AirframeConfig(this);
ui->airframeLayout->addWidget(px4AirframeConfig);
px4SensorCalibration = new QGCPX4SensorCalibration(this);
ui->sensorLayout->addWidget(px4SensorCalibration);
#ifdef QUPGRADE_SUPPORT
firmwareDialog = new DialogBare(this);
ui->firmwareLayout->addWidget(firmwareDialog);
connect(firmwareDialog, SIGNAL(connectLinks()), LinkManager::instance(), SLOT(connectAll()));
connect(firmwareDialog, SIGNAL(disconnectLinks()), LinkManager::instance(), SLOT(disconnectAll()));
#else
#error Please check out QUpgrade from http://github.com/LorenzMeier/qupgrade/ into the QGroundControl folder.
QLabel* label = new QLabel(this);
label->setText("THIS VERSION OF QGROUNDCONTROL WAS BUILT WITHOUT QUPGRADE. To enable firmware upload support, checkout QUpgrade WITHIN the QGroundControl folder");
ui->firmwareLayout->addWidget(label);
#endif
ui->rollWidget->setOrientation(Qt::Horizontal);
ui->rollWidget->setName("Roll");
ui->yawWidget->setOrientation(Qt::Horizontal);
ui->yawWidget->setName("Yaw");
ui->pitchWidget->setName("Pitch");
ui->throttleWidget->setName("Throttle");
ui->radio5Widget->setOrientation(Qt::Horizontal);
ui->radio5Widget->setName("Radio 5");
ui->radio6Widget->setOrientation(Qt::Horizontal);
ui->radio6Widget->setName("Radio 6");
ui->radio7Widget->setOrientation(Qt::Horizontal);
ui->radio7Widget->setName("Radio 7");
ui->radio8Widget->setOrientation(Qt::Horizontal);
ui->radio8Widget->setName("Radio 8");
connect(ui->rcMenuButton,SIGNAL(clicked()),
this,SLOT(rcMenuButtonClicked()));
connect(ui->sensorMenuButton,SIGNAL(clicked()),
this,SLOT(sensorMenuButtonClicked()));
connect(ui->generalMenuButton,SIGNAL(clicked()),
this,SLOT(generalMenuButtonClicked()));
connect(ui->advancedMenuButton,SIGNAL(clicked()),
this,SLOT(advancedMenuButtonClicked()));
connect(ui->airframeMenuButton, SIGNAL(clicked()),
this, SLOT(airframeMenuButtonClicked()));
connect(ui->firmwareMenuButton, SIGNAL(clicked()),
this, SLOT(firmwareMenuButtonClicked()));
connect(ui->advancedCheckBox, SIGNAL(clicked(bool)), ui->advancedGroupBox, SLOT(setVisible(bool)));
ui->advancedGroupBox->setVisible(false);
connect(ui->advancedCheckBox, SIGNAL(clicked(bool)), ui->graphicsView, SLOT(setHidden(bool)));
ui->graphicsView->setScene(&scene);
scene.addPixmap(planeBack);
scene.addPixmap(planeSide);
// XXX hide while WIP
ui->graphicsView->hide();
ui->rcCalibrationButton->setCheckable(true);
ui->rcCalibrationButton->setEnabled(false);
connect(ui->rcCalibrationButton, SIGNAL(clicked(bool)), this, SLOT(toggleCalibrationRC(bool)));
ui->spektrumPairButton->setCheckable(true);
ui->spektrumPairButton->setEnabled(false);
connect(ui->spektrumPairButton, SIGNAL(clicked(bool)), this, SLOT(toggleSpektrumPairing(bool)));
//TODO connect buttons here to save/clear actions?
UASInterface* tmpMav = UASManager::instance()->getActiveUAS();
if (tmpMav) {
ui->pendingCommitsWidget->initWithUAS(tmpMav);
ui->pendingCommitsWidget->update();
setActiveUAS(tmpMav);
}
connect(UASManager::instance(), SIGNAL(activeUASSet(UASInterface*)),
this, SLOT(setActiveUAS(UASInterface*)));
// Connect RC mapping assignments
connect(ui->rollSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setRollChan(int)));
connect(ui->pitchSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setPitchChan(int)));
connect(ui->yawSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setYawChan(int)));
connect(ui->throttleSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setThrottleChan(int)));
connect(ui->modeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setModeChan(int)));
connect(ui->assistSwSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setAssistChan(int)));
connect(ui->missionSwSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setMissionChan(int)));
connect(ui->returnSwSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setReturnChan(int)));
connect(ui->flapsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setFlapsChan(int)));
connect(ui->aux1SpinBox, SIGNAL(valueChanged(int)), this, SLOT(setAux1Chan(int)));
connect(ui->aux2SpinBox, SIGNAL(valueChanged(int)), this, SLOT(setAux2Chan(int)));
// Connect RC reverse assignments
connect(ui->invertCheckBox, SIGNAL(clicked(bool)), this, SLOT(setRollInverted(bool)));
connect(ui->invertCheckBox_2, SIGNAL(clicked(bool)), this, SLOT(setPitchInverted(bool)));
connect(ui->invertCheckBox_3, SIGNAL(clicked(bool)), this, SLOT(setYawInverted(bool)));
connect(ui->invertCheckBox_4, SIGNAL(clicked(bool)), this, SLOT(setThrottleInverted(bool)));
connect(ui->invertCheckBox_5, SIGNAL(clicked(bool)), this, SLOT(setModeInverted(bool)));
connect(ui->assistSwInvertCheckBox, SIGNAL(clicked(bool)), this, SLOT(setAssistInverted(bool)));
connect(ui->missionSwInvertCheckBox, SIGNAL(clicked(bool)), this, SLOT(setMissionInverted(bool)));
connect(ui->returnSwInvertCheckBox, SIGNAL(clicked(bool)), this, SLOT(setReturnInverted(bool)));
connect(ui->flapsInvertCheckBox, SIGNAL(clicked(bool)), this, SLOT(setFlapsInverted(bool)));
connect(ui->aux1InvertCheckBox, SIGNAL(clicked(bool)), this, SLOT(setAux1Inverted(bool)));
connect(ui->aux2InvertCheckBox, SIGNAL(clicked(bool)), this, SLOT(setAux2Inverted(bool)));
connect(ui->rollButton, SIGNAL(clicked()), this, SLOT(identifyRollChannel()));
connect(ui->pitchButton, SIGNAL(clicked()), this, SLOT(identifyPitchChannel()));
connect(ui->yawButton, SIGNAL(clicked()), this, SLOT(identifyYawChannel()));
connect(ui->throttleButton, SIGNAL(clicked()), this, SLOT(identifyThrottleChannel()));
connect(ui->modeButton, SIGNAL(clicked()), this, SLOT(identifyModeChannel()));
connect(ui->assistSwButton, SIGNAL(clicked()), this, SLOT(identifyAssistChannel()));
connect(ui->missionSwButton, SIGNAL(clicked()), this, SLOT(identifyMissionChannel()));
connect(ui->returnSwButton, SIGNAL(clicked()), this, SLOT(identifyReturnChannel()));
connect(ui->flapsButton, SIGNAL(clicked()), this, SLOT(identifyFlapsChannel()));
connect(ui->aux1Button, SIGNAL(clicked()), this, SLOT(identifyAux1Channel()));
connect(ui->aux2Button, SIGNAL(clicked()), this, SLOT(identifyAux2Channel()));
connect(ui->persistRcValuesButt,SIGNAL(clicked()), this, SLOT(writeCalibrationRC()));
//set rc values to defaults
for (unsigned int i = 0; i < chanMax; i++) {
rcValue[i] = UINT16_MAX;
rcValueReversed[i] = UINT16_MAX;
rcMapping[i] = i;
rcToFunctionMapping[i] = i;
channelWantedList[i] = (float)UINT16_MAX;//TODO need to clean these up!
rcMin[i] = 1000.0f;
rcMax[i] = 2000.0f;
// Mapping not established here, so can't pick values via mapping yet!
rcMappedMin[i] = 1000;
rcMappedMax[i] = 2000;
rcMappedValue[i] = UINT16_MAX;
rcMappedValueRev[i] = UINT16_MAX;
rcMappedNormalizedValue[i] = 0.0f;
}
for (unsigned int i = chanMax -1; i < chanMappedMax; i++) {
rcMapping[i] = -1;
rcMappedMin[i] = 1000;
rcMappedMax[i] = 2000;
rcMappedValue[i] = UINT16_MAX;
rcMappedValueRev[i] = UINT16_MAX;
rcMappedNormalizedValue[i] = 0.0f;
}
firmwareMenuButtonClicked();
updateTimer.setInterval(150);
connect(&updateTimer, SIGNAL(timeout()), this, SLOT(updateView()));
updateTimer.start();
ui->rcLabel->setText(tr("NO RADIO CONTROL INPUT DETECTED. PLEASE ENSURE THE TRANSMITTER IS ON."));
}
QGCPX4VehicleConfig::~QGCPX4VehicleConfig()
{
delete ui;
}
void QGCPX4VehicleConfig::rcMenuButtonClicked()
{
//TODO eg ui->stackedWidget->findChild("rcConfig");
ui->stackedWidget->setCurrentIndex(WIDGET_INDEX_RC);
ui->tabTitleLabel->setText(tr("Radio Calibration"));
}
void QGCPX4VehicleConfig::sensorMenuButtonClicked()
{
ui->stackedWidget->setCurrentIndex(WIDGET_INDEX_SENSOR_CAL);
ui->tabTitleLabel->setText(tr("Sensor Calibration"));
}
void QGCPX4VehicleConfig::generalMenuButtonClicked()
{
ui->stackedWidget->setCurrentIndex(WIDGET_INDEX_GENERAL_CONFIG);
ui->tabTitleLabel->setText(tr("General Configuration Options"));
}
void QGCPX4VehicleConfig::advancedMenuButtonClicked()
{
ui->stackedWidget->setCurrentIndex(WIDGET_INDEX_ADV_CONFIG);
ui->tabTitleLabel->setText(tr("Advanced Configuration Options"));
}
void QGCPX4VehicleConfig::airframeMenuButtonClicked()
{
ui->stackedWidget->setCurrentIndex(WIDGET_INDEX_AIRFRAME_CONFIG);
ui->tabTitleLabel->setText(tr("Airframe Configuration"));
}
void QGCPX4VehicleConfig::firmwareMenuButtonClicked()
{
ui->stackedWidget->setCurrentIndex(WIDGET_INDEX_FIRMWARE);
ui->tabTitleLabel->setText(tr("Firmware Upgrade"));
}
void QGCPX4VehicleConfig::identifyChannelMapping(int aert_index)
{
if (chanCount == 0 || aert_index < 0)
return;
int oldmapping = rcMapping[aert_index];
channelWanted = aert_index;
for (unsigned i = 0; i < chanMax; i++) {
if (i >= chanCount) {
channelWantedList[i] = 0;
}
else {
channelWantedList[i] = rcValue[i];
}
}
msgBox.setText(tr("Detecting %1 ...\t\t").arg(channelNames[channelWanted]));
msgBox.setInformativeText(tr("Please move stick, switch or potentiometer for this channel all the way up/down or left/right."));
msgBox.setStandardButtons(QMessageBox::NoButton);
skipActionButton = msgBox.addButton(tr("Skip"),QMessageBox::RejectRole);
msgBox.exec();
skipActionButton->hide();
msgBox.removeButton(skipActionButton);
if (msgBox.clickedButton() == skipActionButton ){
channelWanted = -1;
rcMapping[aert_index] = oldmapping;
}
skipActionButton = NULL;
}
void QGCPX4VehicleConfig::toggleCalibrationRC(bool enabled)
{
if (enabled)
{
startCalibrationRC();
}
else
{
stopCalibrationRC();
}
}
void QGCPX4VehicleConfig::toggleSpektrumPairing(bool enabled)
{
if (!ui->dsm2RadioButton->isChecked() && !ui->dsmxRadioButton) {
// Reject
QMessageBox warnMsgBox;
warnMsgBox.setText(tr("Please select a Spektrum Protocol Version"));
warnMsgBox.setInformativeText(tr("Please select either DSM2 or DSM-X\ndirectly below the pair button,\nbased on the receiver type."));
warnMsgBox.setStandardButtons(QMessageBox::Ok);
warnMsgBox.setDefaultButton(QMessageBox::Ok);
(void)warnMsgBox.exec();
}
if (enabled)
{
int mode = 1; // DSM2
if (ui->dsmxRadioButton->isChecked())
mode = 2; // DSMX
mav->getParamManager()->setPendingParam(0, "RC_DSM_BIND", mode);
// Do not save this parameter, just set in RAM
mav->getParamManager()->sendPendingParameters();
}
else
{
mav->getParamManager()->setPendingParam(0, "RC_DSM_BIND", (int)0);
// Do not save this parameter, just set in RAM
mav->getParamManager()->sendPendingParameters();
}
}
void QGCPX4VehicleConfig::setTrimPositions()
{
int rollMap = rcMapping[0];
int pitchMap = rcMapping[1];
int yawMap = rcMapping[2];
int throttleMap = rcMapping[3];
// Reset all trims, as some might not be touched
for (unsigned i = 0; i < chanCount; i++) {
rcTrim[i] = 1500;
}
// Set trim to min if stick is close to min
if (abs(rcValue[throttleMap] - rcMin[throttleMap]) < 100) {
rcTrim[throttleMap] = rcMin[throttleMap]; // throttle
}
// Set trim to max if stick is close to max
else if (abs(rcValue[throttleMap] - rcMax[throttleMap]) < 100) {
rcTrim[throttleMap] = rcMax[throttleMap]; // throttle
}
else {
// Reject
QMessageBox warnMsgBox;
warnMsgBox.setText(tr("Throttle Stick Trim Position Invalid"));
warnMsgBox.setInformativeText(tr("The throttle stick is not in the min position. Please set it to the minimum value"));
warnMsgBox.setStandardButtons(QMessageBox::Ok);
warnMsgBox.setDefaultButton(QMessageBox::Ok);
(void)warnMsgBox.exec();
}
// Set trim for roll, pitch, yaw, throttle
rcTrim[rollMap] = rcValue[rollMap]; // roll
rcTrim[pitchMap] = rcValue[pitchMap]; // pitch
rcTrim[yawMap] = rcValue[yawMap]; // yaw
// Mode switch and optional modes, might not be mapped (== -1)
for (unsigned i = 4; i < chanMappedMax; i++) {
if (rcMapping[i] >= 0 && rcMapping[i] < (int)chanCount) {
rcTrim[rcMapping[i]] = ((rcMax[rcMapping[i]] - rcMin[rcMapping[i]]) / 2.0f) + rcMin[rcMapping[i]];
} else if (rcMapping[i] != -1){
qDebug() << "RC MAPPING FAILED #" << i << "VAL:" << rcMapping[i];
}
}
}
void QGCPX4VehicleConfig::detectChannelInversion(int aert_index)
{
if (chanCount == 0 || aert_index < 0 || aert_index >= (int)chanMappedMax)
return;
bool oldstatus = rcRev[rcMapping[aert_index]];
channelReverseStateWanted = aert_index;
// Reset search list
for (unsigned i = 0; i < chanMax; i++) {
if (i >= chanCount) {
channelReverseStateWantedList[i] = 0;
}
else {
channelReverseStateWantedList[i] = rcValue[i];
}
}
QStringList instructions;
instructions << "ROLL: Move stick left";
instructions << "PITCH: Move stick down";//matches the other sticks: should cause DECREASE in raw rc channel value when not reversed
instructions << "YAW: Move stick left";
instructions << "THROTTLE: Move stick down";
instructions << "MODE SWITCH: Push down / towards you";
instructions << "ASSISTED SWITCH: Push down / towards you";
instructions << "MISSION SWITCH: Push down / towards you";
instructions << "RETURN SWITCH: Push down / towards you";
instructions << "FLAPS: Push down / towards you or turn dial to the leftmost position";
instructions << "AUX1: Push down / towards you or turn dial to the leftmost position";
instructions << "AUX2: Push down / towards you or turn dial to the leftmost position";
msgBox.setText(tr("%1 Direction").arg(channelNames[channelReverseStateWanted]));
msgBox.setInformativeText(tr("%2").arg((aert_index < instructions.length()) ? instructions[aert_index] : ""));
msgBox.setStandardButtons(QMessageBox::NoButton);
skipActionButton = msgBox.addButton(tr("Skip"),QMessageBox::RejectRole);
msgBox.exec();
skipActionButton->hide();
msgBox.removeButton(skipActionButton);
if (msgBox.clickedButton() == skipActionButton ){
channelReverseStateWanted = -1;
rcRev[rcMapping[aert_index]] = oldstatus;
}
skipActionButton = NULL;
}
void QGCPX4VehicleConfig::startCalibrationRC()
{
if (chanCount < 5) {
QMessageBox::warning(0,
tr("RC not Connected"),
tr("Is the RC receiver connected and transmitter turned on? Detected %1 radio channels. To operate PX4, you need at least 5 channels. ").arg(chanCount));
ui->rcCalibrationButton->setChecked(false);
return;
}
// reset all channel mappings above Ch 5 to invalid/unused value before starting calibration
for (unsigned int j= 5; j < chanMappedMax; j++) {
rcMapping[j] = -1;
}
configEnabled = true;
QMessageBox::warning(0,tr("Safety Warning"),
tr("Starting RC calibration.\n\nEnsure that motor power is disconnected, all props are removed, RC transmitter and receiver are powered and connected.\n\nReset transmitter trims to center, then click OK to continue"));
//go ahead and try to map first 8 channels, now that user can skip channels
for (int i = 0; i < 8; i++) {
identifyChannelMapping(i);
}
// Try to identify inverted channels, but only for R/P/Y/T
for (int i = 0; i < 4; i++) {
detectChannelInversion(i);
}
//QMessageBox::information(0,"Information","Additional channels have not been mapped, but can be mapped in the channel table below.");
configEnabled = false;
QMessageBox::information(0,"Information","Click OK, then move all sticks to their extreme positions, watching the min/max values to ensure you get the most range from your controller. This includes all switches");
ui->rcCalibrationButton->setText(tr("Save RC Calibration"));
resetCalibrationRC();
calibrationEnabled = true;
ui->rollWidget->showMinMax();
ui->pitchWidget->showMinMax();
ui->yawWidget->showMinMax();
ui->throttleWidget->showMinMax();
ui->radio5Widget->showMinMax();
ui->radio6Widget->showMinMax();
ui->radio7Widget->showMinMax();
ui->radio8Widget->showMinMax();
}
void QGCPX4VehicleConfig::stopCalibrationRC()
{
if (!calibrationEnabled)
return;
QMessageBox::information(0,"Trims","Ensure all controls are centered and throttle is in the lowest position. Click OK to continue");
calibrationEnabled = false;
configEnabled = false;
ui->rcCalibrationButton->setText(tr("Start RC Calibration"));
ui->rollWidget->hideMinMax();
ui->pitchWidget->hideMinMax();
ui->yawWidget->hideMinMax();
ui->throttleWidget->hideMinMax();
ui->radio5Widget->hideMinMax();
ui->radio6Widget->hideMinMax();
ui->radio7Widget->hideMinMax();
ui->radio8Widget->hideMinMax();
for (unsigned int i = 0; i < chanCount; i++) {
if (rcMin[i] > 1350) {
rcMin[i] = 1000;
}
if (rcMax[i] < 1650) {
rcMax[i] = 2000;
}
}
qDebug() << "SETTING TRIM";
setTrimPositions();
QString statusstr;
statusstr = tr("This is the RC calibration information that will be sent to the autopilot if you click OK. To prevent transmission, click Cancel.");
statusstr += tr(" Normal values range from 1000 to 2000, with disconnected channels reading 1000, 1500, 2000\n\n");
statusstr += tr("Channel\tMin\tCenter\tMax\n");
statusstr += "-------\t---\t------\t---\n";
for (unsigned int i=0; i < chanCount; i++) {
statusstr += QString::number(i) +"\t"+ QString::number(rcMin[i]) +"\t"+ QString::number(rcValue[i]) +"\t"+ QString::number(rcMax[i]) +"\n";
}
msgBox.setText(tr("Confirm Calibration"));
msgBox.setInformativeText(statusstr);
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);//allow user to cancel upload after reviewing values
int msgBoxResult = msgBox.exec();
if (QMessageBox::Cancel == msgBoxResult) {
return;//don't commit these values
}
QMessageBox::information(0,"Uploading the RC Calibration","The configuration will now be uploaded and permanently stored.");
writeCalibrationRC();
}
void QGCPX4VehicleConfig::loadQgcConfig(bool primary)
{
Q_UNUSED(primary);
QDir autopilotdir(qApp->applicationDirPath() + "/files/" + mav->getAutopilotTypeName().toLower());
QDir generaldir = QDir(autopilotdir.absolutePath() + "/general/widgets");
QDir vehicledir = QDir(autopilotdir.absolutePath() + "/" + mav->getSystemTypeName().toLower() + "/widgets");
if (!autopilotdir.exists("general"))
{
//TODO: Throw some kind of error here. There is no general configuration directory
qWarning() << "Invalid general dir. no general configuration will be loaded.";
}
if (!autopilotdir.exists(mav->getAutopilotTypeName().toLower()))
{
//TODO: Throw an error here too, no autopilot specific configuration
qWarning() << "Invalid vehicle dir, no vehicle specific configuration will be loaded.";
}
// Generate widgets for the General tab.
QGCToolWidget *tool;
bool left = true;
foreach (QString file,generaldir.entryList(QDir::Files | QDir::NoDotAndDotDot))
{
if (file.toLower().endsWith(".qgw")) {
QWidget* parent = left?ui->generalLeftContents:ui->generalRightContents;
tool = new QGCToolWidget("", parent);
if (tool->loadSettings(generaldir.absoluteFilePath(file), false))
{
toolWidgets.append(tool);
QGroupBox *box = new QGroupBox(parent);
box->setTitle(tool->objectName());
box->setLayout(new QVBoxLayout(box));
box->layout()->addWidget(tool);
if (left)
{
left = false;
ui->generalLeftLayout->addWidget(box);
}
else
{
left = true;
ui->generalRightLayout->addWidget(box);
}
} else {
delete tool;
}
}
}
//TODO fix and reintegrate the Advanced parameter editor
// // Generate widgets for the Advanced tab.
// foreach (QString file,vehicledir.entryList(QDir::Files | QDir::NoDotAndDotDot))
// {
// if (file.toLower().endsWith(".qgw")) {
// QWidget* parent = ui->advanceColumnContents;
// tool = new QGCToolWidget("", parent);
// if (tool->loadSettings(vehicledir.absoluteFilePath(file), false))
// {
// toolWidgets.append(tool);
// QGroupBox *box = new QGroupBox(parent);
// box->setTitle(tool->objectName());
// box->setLayout(new QVBoxLayout(box));
// box->layout()->addWidget(tool);
// ui->advancedColumnLayout->addWidget(box);
// } else {
// delete tool;
// }
// }
// }
// Load tabs for general configuration
foreach (QString dir,generaldir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
{
QPushButton *button = new QPushButton(this);
connect(button,SIGNAL(clicked()),this,SLOT(menuButtonClicked()));
ui->navBarLayout->insertWidget(2,button);
button->setMinimumHeight(75);
button->setMinimumWidth(100);
button->show();
button->setText(dir);
QWidget *tab = new QWidget(ui->stackedWidget);
ui->stackedWidget->insertWidget(2,tab);
buttonToWidgetMap[button] = tab;
tab->setLayout(new QVBoxLayout());
tab->show();
QScrollArea *area = new QScrollArea(tab);
tab->layout()->addWidget(area);
QWidget *scrollArea = new QWidget(tab);
scrollArea->setLayout(new QVBoxLayout(tab));
area->setWidget(scrollArea);
area->setWidgetResizable(true);
area->show();
scrollArea->show();
QDir newdir = QDir(generaldir.absoluteFilePath(dir));
foreach (QString file,newdir.entryList(QDir::Files| QDir::NoDotAndDotDot))
{
if (file.toLower().endsWith(".qgw")) {
tool = new QGCToolWidget("", tab);
if (tool->loadSettings(newdir.absoluteFilePath(file), false))
{
toolWidgets.append(tool);
//ui->sensorLayout->addWidget(tool);
QGroupBox *box = new QGroupBox(tab);
box->setTitle(tool->objectName());
box->setLayout(new QVBoxLayout(tab));
box->layout()->addWidget(tool);
scrollArea->layout()->addWidget(box);
} else {
delete tool;
}
}
}
}
// Load additional tabs for vehicle specific configuration
foreach (QString dir,vehicledir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
{
QPushButton *button = new QPushButton(this);
connect(button,SIGNAL(clicked()),this,SLOT(menuButtonClicked()));
ui->navBarLayout->insertWidget(2,button);
QWidget *tab = new QWidget(ui->stackedWidget);
ui->stackedWidget->insertWidget(2,tab);
buttonToWidgetMap[button] = tab;
button->setMinimumHeight(75);
button->setMinimumWidth(100);
button->show();
button->setText(dir);
tab->setLayout(new QVBoxLayout());
tab->show();
QScrollArea *area = new QScrollArea(tab);
tab->layout()->addWidget(area);
QWidget *scrollArea = new QWidget(tab);
scrollArea->setLayout(new QVBoxLayout(tab));
area->setWidget(scrollArea);
area->setWidgetResizable(true);
area->show();
scrollArea->show();
QDir newdir = QDir(vehicledir.absoluteFilePath(dir));
foreach (QString file,newdir.entryList(QDir::Files| QDir::NoDotAndDotDot))
{
if (file.toLower().endsWith(".qgw")) {
tool = new QGCToolWidget("", tab);
tool->addUAS(mav);
if (tool->loadSettings(newdir.absoluteFilePath(file), false))
{
toolWidgets.append(tool);
//ui->sensorLayout->addWidget(tool);
QGroupBox *box = new QGroupBox(tab);
box->setTitle(tool->objectName());
box->setLayout(new QVBoxLayout(box));
box->layout()->addWidget(tool);
scrollArea->layout()->addWidget(box);
box->show();
//gbox->layout()->addWidget(box);
} else {
delete tool;
}
}
}
}
}
void QGCPX4VehicleConfig::menuButtonClicked()
{
QPushButton *button = qobject_cast<QPushButton*>(sender());
if (!button)
{
return;
}
if (buttonToWidgetMap.contains(button))
{
ui->stackedWidget->setCurrentWidget(buttonToWidgetMap[button]);
}
}
void QGCPX4VehicleConfig::loadConfig()
{
QGCToolWidget* tool;
QDir autopilotdir(qApp->applicationDirPath() + "/files/" + mav->getAutopilotTypeName().toLower());
QDir generaldir = QDir(autopilotdir.absolutePath() + "/general/widgets");
QDir vehicledir = QDir(autopilotdir.absolutePath() + "/" + mav->getSystemTypeName().toLower() + "/widgets");
if (!autopilotdir.exists("general"))
{
//TODO: Throw some kind of error here. There is no general configuration directory
qWarning() << "Invalid general dir. no general configuration will be loaded.";
}
if (!autopilotdir.exists(mav->getAutopilotTypeName().toLower()))
{
//TODO: Throw an error here too, no autopilot specific configuration
qWarning() << "Invalid vehicle dir, no vehicle specific configuration will be loaded.";
}
qDebug() << autopilotdir.absolutePath();
qDebug() << generaldir.absolutePath();
qDebug() << vehicledir.absolutePath();
QFile xmlfile(autopilotdir.absolutePath() + "/arduplane.pdef.xml");
if (xmlfile.exists() && !xmlfile.open(QIODevice::ReadOnly))
{
loadQgcConfig(false);
doneLoadingConfig = true;
return;
}
loadQgcConfig(true);
QXmlStreamReader xml(xmlfile.readAll());
xmlfile.close();
//TODO: Testing to ensure that incorrectly formatted XML won't break this.
while (!xml.atEnd())
{
if (xml.isStartElement() && xml.name() == "paramfile")
{
xml.readNext();
while ((xml.name() != "paramfile") && !xml.atEnd())
{
QString valuetype = "";
if (xml.isStartElement() && (xml.name() == "vehicles" || xml.name() == "libraries")) //Enter into the vehicles loop
{
valuetype = xml.name().toString();
xml.readNext();
while ((xml.name() != valuetype) && !xml.atEnd())
{
if (xml.isStartElement() && xml.name() == "parameters") //This is a parameter block
{
QString parametersname = "";
if (xml.attributes().hasAttribute("name"))
{
parametersname = xml.attributes().value("name").toString();
}
QVariantMap genset;
QVariantMap advset;
QString setname = parametersname;
xml.readNext();
int genarraycount = 0;
int advarraycount = 0;
while ((xml.name() != "parameters") && !xml.atEnd())
{
if (xml.isStartElement() && xml.name() == "param")
{
QString humanname = xml.attributes().value("humanName").toString();
QString name = xml.attributes().value("name").toString();
QString tab= xml.attributes().value("user").toString();
if (tab == "Advanced")
{
advset["title"] = parametersname;
}
else
{
genset["title"] = parametersname;
}
if (name.contains(":"))
{
name = name.split(":")[1];
}
QString docs = xml.attributes().value("documentation").toString();
paramTooltips[name] = name + " - " + docs;
int type = -1; //Type of item
QMap<QString,QString> fieldmap;
xml.readNext();
while ((xml.name() != "param") && !xml.atEnd())
{
if (xml.isStartElement() && xml.name() == "values")
{
type = 1; //1 is a combobox
if (tab == "Advanced")
{
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "TYPE"] = "COMBO";
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_COMBOBOX_DESCRIPTION"] = humanname;
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_COMBOBOX_PARAMID"] = name;
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_COMBOBOX_COMPONENTID"] = 1;
}
else
{
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "TYPE"] = "COMBO";
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_COMBOBOX_DESCRIPTION"] = humanname;
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_COMBOBOX_PARAMID"] = name;
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_COMBOBOX_COMPONENTID"] = 1;
}
int paramcount = 0;
xml.readNext();
while ((xml.name() != "values") && !xml.atEnd())
{
if (xml.isStartElement() && xml.name() == "value")
{
QString code = xml.attributes().value("code").toString();
QString arg = xml.readElementText();
if (tab == "Advanced")
{
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_COMBOBOX_ITEM_" + QString::number(paramcount) + "_TEXT"] = arg;
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_COMBOBOX_ITEM_" + QString::number(paramcount) + "_VAL"] = code.toInt();
}
else
{
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_COMBOBOX_ITEM_" + QString::number(paramcount) + "_TEXT"] = arg;
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_COMBOBOX_ITEM_" + QString::number(paramcount) + "_VAL"] = code.toInt();
}
paramcount++;
}
xml.readNext();
}
if (tab == "Advanced")
{
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_COMBOBOX_COUNT"] = paramcount;
}
else
{
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_COMBOBOX_COUNT"] = paramcount;
}
}
if (xml.isStartElement() && xml.name() == "field")
{
type = 2; //2 is a slider
QString fieldtype = xml.attributes().value("name").toString();
QString text = xml.readElementText();
fieldmap[fieldtype] = text;
}
xml.readNext();
}
if (type == -1)
{
//Nothing inside! Assume it's a value, give it a default range.
type = 2;
QString fieldtype = "Range";
QString text = "0 100"; //TODO: Determine a better way of figuring out default ranges.
fieldmap[fieldtype] = text;
}
if (type == 2)
{
if (tab == "Advanced")
{
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "TYPE"] = "SLIDER";
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_SLIDER_DESCRIPTION"] = humanname;
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_SLIDER_PARAMID"] = name;
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_SLIDER_COMPONENTID"] = 1;
}
else
{
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "TYPE"] = "SLIDER";
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_SLIDER_DESCRIPTION"] = humanname;
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_SLIDER_PARAMID"] = name;
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_SLIDER_COMPONENTID"] = 1;
}
if (fieldmap.contains("Range"))
{
float min = 0;
float max = 0;
//Some range fields list "0-10" and some list "0 10". Handle both.
if (fieldmap["Range"].split(" ").size() > 1)
{
min = fieldmap["Range"].split(" ")[0].trimmed().toFloat();
max = fieldmap["Range"].split(" ")[1].trimmed().toFloat();
}
else if (fieldmap["Range"].split("-").size() > 1)
{
min = fieldmap["Range"].split("-")[0].trimmed().toFloat();
max = fieldmap["Range"].split("-")[1].trimmed().toFloat();
}
if (tab == "Advanced")
{
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_SLIDER_MIN"] = min;
advset[setname + "\\" + QString::number(advarraycount) + "\\" + "QGC_PARAM_SLIDER_MAX"] = max;
}
else
{
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_SLIDER_MIN"] = min;
genset[setname + "\\" + QString::number(genarraycount) + "\\" + "QGC_PARAM_SLIDER_MAX"] = max;
}
}
}
if (tab == "Advanced")
{
advarraycount++;
advset["count"] = advarraycount;
}
else
{
genarraycount++;
genset["count"] = genarraycount;
}
}
xml.readNext();
}
if (genarraycount > 0)
{
QWidget* parent = this;
if (valuetype == "vehicles")
{
parent = ui->generalLeftContents;
}
else if (valuetype == "libraries")
{
parent = ui->generalRightContents;
}
tool = new QGCToolWidget("", parent);
tool->addUAS(mav);
tool->setTitle(parametersname);
tool->setObjectName(parametersname);
tool->setSettings(genset);
QList<QString> paramlist = tool->getParamList();
for (int i=0;i<paramlist.size();i++)
{
//Based on the airframe, we add the parameter to different categories.
if (parametersname == "ArduPlane") //MAV_TYPE_FIXED_WING FIXED_WING
{
systemTypeToParamMap["FIXED_WING"].insert(paramlist[i],tool);
}
else if (parametersname == "ArduCopter") //MAV_TYPE_QUADROTOR "QUADROTOR
{
systemTypeToParamMap["QUADROTOR"].insert(paramlist[i],tool);
}
else if (parametersname == "APMrover2") //MAV_TYPE_GROUND_ROVER GROUND_ROVER
{
systemTypeToParamMap["GROUND_ROVER"].insert(paramlist[i],tool);
}
else
{
libParamToWidgetMap.insert(paramlist[i],tool);
}
}
toolWidgets.append(tool);
QGroupBox *box = new QGroupBox(parent);
box->setTitle(tool->objectName());
box->setLayout(new QVBoxLayout(box));
box->layout()->addWidget(tool);
if (valuetype == "vehicles")
{
ui->generalLeftLayout->addWidget(box);
}
else if (valuetype == "libraries")
{
ui->generalRightLayout->addWidget(box);
}
box->hide();
toolToBoxMap[tool] = box;
}
if (advarraycount > 0)
{
QWidget* parent = this;
if (valuetype == "vehicles")
{
parent = ui->generalLeftContents;
}
else if (valuetype == "libraries")
{
parent = ui->generalRightContents;
}
tool = new QGCToolWidget("", parent);
tool->addUAS(mav);
tool->setTitle(parametersname);
tool->setObjectName(parametersname);
tool->setSettings(advset);
QList<QString> paramlist = tool->getParamList();
for (int i=0;i<paramlist.size();i++)
{
//Based on the airframe, we add the parameter to different categories.
if (parametersname == "ArduPlane") //MAV_TYPE_FIXED_WING FIXED_WING
{
systemTypeToParamMap["FIXED_WING"].insert(paramlist[i],tool);
}
else if (parametersname == "ArduCopter") //MAV_TYPE_QUADROTOR "QUADROTOR
{
systemTypeToParamMap["QUADROTOR"].insert(paramlist[i],tool);
}
else if (parametersname == "APMrover2") //MAV_TYPE_GROUND_ROVER GROUND_ROVER
{
systemTypeToParamMap["GROUND_ROVER"].insert(paramlist[i],tool);
}
else
{
libParamToWidgetMap.insert(paramlist[i],tool);
}
}
toolWidgets.append(tool);
QGroupBox *box = new QGroupBox(parent);
box->setTitle(tool->objectName());
box->setLayout(new QVBoxLayout(box));
box->layout()->addWidget(tool);
if (valuetype == "vehicles")
{
ui->generalLeftLayout->addWidget(box);
}
else if (valuetype == "libraries")
{
ui->generalRightLayout->addWidget(box);
}
box->hide();
toolToBoxMap[tool] = box;
}
}
xml.readNext();
}
}
xml.readNext();
}
}
xml.readNext();
}
if (!paramTooltips.isEmpty()) {
paramMgr->setParamDescriptions(paramTooltips);
}
doneLoadingConfig = true;
//Config is finished, lets do a parameter request to ensure none are missed if someone else started requesting before we were finished.
paramMgr->requestParameterListIfEmpty();
}
void QGCPX4VehicleConfig::setActiveUAS(UASInterface* active)
{
// Hide items if NULL and abort
if (!active) {
return;
}
// Do nothing if UAS is already visible
if (mav == active)
return;
if (mav)
{
// Disconnect old system
disconnect(mav, SIGNAL(remoteControlChannelRawChanged(int,float)), this,
SLOT(remoteControlChannelRawChanged(int,float)));
//TODO use paramCommsMgr instead
disconnect(mav, SIGNAL(parameterChanged(int,int,QString,QVariant)), this,
SLOT(parameterChanged(int,int,QString,QVariant)));
// Delete all children from all fixed tabs.
foreach(QWidget* child, ui->generalLeftContents->findChildren<QWidget*>()) {
child->deleteLater();
}
foreach(QWidget* child, ui->generalRightContents->findChildren<QWidget*>()) {
child->deleteLater();
}
foreach(QWidget* child, ui->advanceColumnContents->findChildren<QWidget*>()) {
child->deleteLater();
}
// foreach(QWidget* child, ui->sensorLayout->findChildren<QWidget*>()) {
// child->deleteLater();
// }
foreach(QWidget* child, ui->airframeLayout->findChildren<QWidget*>())
{
child->deleteLater();
}
// And then delete any custom tabs
foreach(QWidget* child, additionalTabs) {
child->deleteLater();
}
additionalTabs.clear();
toolWidgets.clear();
paramToWidgetMap.clear();
libParamToWidgetMap.clear();
systemTypeToParamMap.clear();
toolToBoxMap.clear();
paramTooltips.clear();
}
// Connect new system
mav = active;
paramMgr = mav->getParamManager();
ui->pendingCommitsWidget->setUAS(mav);
ui->paramTreeWidget->setUAS(mav);
// Reset current state
resetCalibrationRC();
//TODO eliminate the separate RC_TYPE call
mav->requestParameter(0, "RC_TYPE");
chanCount = 0;
// Connect new system
connect(mav, SIGNAL(remoteControlChannelRawChanged(int,float)), this,
SLOT(remoteControlChannelRawChanged(int,float)));
connect(mav, SIGNAL(parameterChanged(int,int,QString,QVariant)), this,
SLOT(parameterChanged(int,int,QString,QVariant)));
if (systemTypeToParamMap.contains(mav->getSystemTypeName())) {
paramToWidgetMap = systemTypeToParamMap[mav->getSystemTypeName()];
}
else {
//Indication that we have no meta data for this system type.
qDebug() << "No parameters defined for system type:" << mav->getSystemTypeName();
paramToWidgetMap = systemTypeToParamMap[mav->getSystemTypeName()];
}
if (!paramTooltips.isEmpty()) {
mav->getParamManager()->setParamDescriptions(paramTooltips);
}
qDebug() << "CALIBRATION!! System Type Name:" << mav->getSystemTypeName();
//Load configuration after 1ms. This allows it to go into the event loop, and prevents application hangups due to the
//amount of time it actually takes to load the configuration windows.
QTimer::singleShot(1,this,SLOT(loadConfig()));
updateStatus(QString("Reading from system %1").arg(mav->getUASName()));
// Since a system is now connected, enable the VehicleConfig UI.
// Enable buttons
ui->advancedMenuButton->setEnabled(true);
ui->airframeMenuButton->setEnabled(true);
ui->sensorMenuButton->setEnabled(true);
ui->rcMenuButton->setEnabled(true);
ui->rcCalibrationButton->setEnabled(true);
ui->spektrumPairButton->setEnabled(true);
}
void QGCPX4VehicleConfig::resetCalibrationRC()
{
for (unsigned int i = 0; i < chanMax; ++i) {
rcMin[i] = 1500;
rcMax[i] = 1500;
}
}
/**
* Sends the RC calibration to the vehicle and stores it in EEPROM
*/
void QGCPX4VehicleConfig::writeCalibrationRC()
{
if (!mav) return;
updateStatus(tr("Sending RC configuration and storing to persistent memory."));
QString minTpl("RC%1_MIN");
QString maxTpl("RC%1_MAX");
QString trimTpl("RC%1_TRIM");
QString revTpl("RC%1_REV");
// Do not write the RC type, as these values depend on this
// active onboard parameter
for (unsigned int i = 0; i < chanCount; ++i) {
//qDebug() << "SENDING" << minTpl.arg(i+1) << rcMin[i];
paramMgr->setPendingParam(0, minTpl.arg(i+1), rcMin[i]);
paramMgr->setPendingParam(0, trimTpl.arg(i+1), rcTrim[i]);
paramMgr->setPendingParam(0, maxTpl.arg(i+1), rcMax[i]);
paramMgr->setPendingParam(0, revTpl.arg(i+1), (rcRev[i]) ? -1.0f : 1.0f);
}
// Write mappings
paramMgr->setPendingParam(0, "RC_MAP_ROLL", (int32_t)(rcMapping[0]+1));
paramMgr->setPendingParam(0, "RC_MAP_PITCH", (int32_t)(rcMapping[1]+1));
paramMgr->setPendingParam(0, "RC_MAP_YAW", (int32_t)(rcMapping[2]+1));
paramMgr->setPendingParam(0, "RC_MAP_THROTTLE", (int32_t)(rcMapping[3]+1));
paramMgr->setPendingParam(0, "RC_MAP_MODE_SW", (int32_t)(rcMapping[4]+1));
paramMgr->setPendingParam(0, "RC_MAP_ASSIST_SW", (int32_t)(rcMapping[5]+1));
paramMgr->setPendingParam(0, "RC_MAP_MISSIO_SW", (int32_t)(rcMapping[6]+1));
paramMgr->setPendingParam(0, "RC_MAP_RETURN_SW", (int32_t)(rcMapping[7]+1));
paramMgr->setPendingParam(0, "RC_MAP_FLAPS", (int32_t)(rcMapping[8]+1));
paramMgr->setPendingParam(0, "RC_MAP_AUX1", (int32_t)(rcMapping[9]+1));
paramMgr->setPendingParam(0, "RC_MAP_AUX2", (int32_t)(rcMapping[10]+1));
//let the param mgr manage sending all the pending RC_foo updates and persisting after
paramMgr->sendPendingParameters(true);
}
void QGCPX4VehicleConfig::requestCalibrationRC()
{
paramMgr->requestRcCalibrationParamsUpdate();
}
void QGCPX4VehicleConfig::writeParameters()
{
updateStatus(tr("Writing all onboard parameters."));
writeCalibrationRC();
}
void QGCPX4VehicleConfig::remoteControlChannelRawChanged(int chan, float fval)
{
// Check if index and values are sane
if (chan < 0 || static_cast<unsigned int>(chan) >= chanMax || fval < 500.0f || fval > 2500.0f)
return;
if (chan + 1 > (int)chanCount) {
chanCount = chan+1;
}
// Raw value
float deltaRaw = fabsf(fval - rcValue[chan]);
float delta = fabsf(fval - rcMappedValue[rcToFunctionMapping[chan]]);
if (!configEnabled && !calibrationEnabled &&
(deltaRaw < 12.0f && delta < 12.0f && rcValue[chan] > 800 && rcValue[chan] < 2200))
{
// ignore tiny jitter values
return;
}
else {
rcValue[chan] = fval;
}
// Update calibration data
if (calibrationEnabled) {
if (fval < rcMin[chan]) {
rcMin[chan] = fval;
}
if (fval > rcMax[chan]) {
rcMax[chan] = fval;
}
}
if (channelWanted >= 0) {
// If the first channel moved considerably, pick it
if (fabsf(channelWantedList[chan] - fval) > 300.0f) {
rcMapping[channelWanted] = chan;
updateMappingView(channelWanted);
int chanFound = channelWanted;
channelWanted = -1;
// Confirm found channel
msgBox.setText(tr("Found %1 \t\t").arg(channelNames[chanFound]));
msgBox.setInformativeText(tr("Assigned raw RC channel %2").arg(chan + 1));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
skipActionButton->hide();
msgBox.removeButton(skipActionButton);
(void)msgBox.exec();
// XXX fuse with parameter update handling
switch (chanFound) {
case 0:
ui->rollSpinBox->setValue(chan + 1);
break;
case 1:
ui->pitchSpinBox->setValue(chan + 1);
break;
case 2:
ui->yawSpinBox->setValue(chan + 1);
break;
case 3:
ui->throttleSpinBox->setValue(chan + 1);
break;
case 4:
ui->modeSpinBox->setValue(chan + 1);
break;
case 5:
ui->assistSwSpinBox->setValue(chan + 1);
break;
case 6:
ui->missionSwSpinBox->setValue(chan + 1);
break;
case 7:
ui->returnSwSpinBox->setValue(chan + 1);
break;
case 8:
ui->flapsSpinBox->setValue(chan + 1);
break;
case 9:
ui->aux1SpinBox->setValue(chan + 1);
break;
case 10:
ui->aux2SpinBox->setValue(chan + 1);
break;
}
}
}
rcValueReversed[chan] = (rcRev[chan]) ? rcMax[chan] - (fval - rcMin[chan]) : fval;
// Normalized value
float normalized;
float chanTrim = rcTrim[chan];
if (fval >= rcTrim[chan]) {
normalized = (fval - chanTrim)/(rcMax[chan] - chanTrim);
}
else {
normalized = -(chanTrim - fval)/(chanTrim - rcMin[chan]);
}
// Bound
normalized = qBound(-1.0f, normalized, 1.0f);
// Invert
normalized = (rcRev[chan]) ? -1.0f*normalized : normalized;
// Find correct mapped channel
rcMappedValueRev[rcToFunctionMapping[chan]] = (rcRev[chan]) ? rcMax[chan] - (fval - rcMin[chan]) : fval;
rcMappedValue[rcToFunctionMapping[chan]] = fval;
// Copy min / max
rcMappedMin[rcToFunctionMapping[chan]] = rcMin[chan];
rcMappedMax[rcToFunctionMapping[chan]] = rcMax[chan];
rcMappedNormalizedValue[rcToFunctionMapping[chan]] = normalized;
if (chan == rcMapping[0]) {
rcRoll = normalized;
}
else if (chan == rcMapping[1]) {
rcPitch = normalized;
}
else if (chan == rcMapping[2]) {
rcYaw = normalized;
}
else if (chan == rcMapping[3]) {
if (rcRev[chan]) {
rcThrottle = 1.0f + normalized;
}
else {
rcThrottle = normalized;
}
rcThrottle = qBound(0.0f, rcThrottle, 1.0f);
}
else if (chan == rcMapping[4]) {
rcMode = normalized; // MODE SWITCH
}
else if (chan == rcMapping[5]) {
rcAssist = normalized; // ASSIST SWITCH
}
else if (chan == rcMapping[6]) {
rcMission = normalized; // MISSION SWITCH
}
else if (chan == rcMapping[7]) {
rcReturn = normalized; // RETURN SWITCH
}
else if (chan == rcMapping[8]) {
rcFlaps = normalized; // FLAPS
}
else if (chan == rcMapping[9]) {
rcAux1 = normalized; // AUX2
}
else if (chan == rcMapping[10]) {
rcAux2 = normalized; // AUX3
}
if (channelReverseStateWanted >= 0) {
// If the *right* channel moved considerably, evaluate it
if (fabsf(fval - 1500) > 350.0f &&
rcMapping[channelReverseStateWanted] == chan) {
// Check if the output is positive
if (fval > 1750) {
rcRev[rcMapping[channelReverseStateWanted]] = true;
} else {
rcRev[rcMapping[channelReverseStateWanted]] = false;
}
unsigned currRevFunc = channelReverseStateWanted;
channelReverseStateWanted = -1;
// Confirm found channel
msgBox.setText(tr("%1 direction assigned").arg(channelNames[currRevFunc]));
msgBox.setInformativeText(tr("%1").arg((rcRev[rcMapping[currRevFunc]]) ? tr("Reversed channel.") : tr("Did not reverse channel.") ));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
skipActionButton->hide();
msgBox.removeButton(skipActionButton);
(void)msgBox.exec();
}
}
dataModelChanged = true;
//qDebug() << "RC CHAN:" << chan << "PPM:" << fval << "NORMALIZED:" << normalized;
}
void QGCPX4VehicleConfig::updateAllInvertedCheckboxes()
{
for (unsigned function_index = 0; function_index < chanMappedMax; function_index++) {
int rc_input_index = rcMapping[function_index];
if (rc_input_index < 0 || rc_input_index > (int)chanMax)
continue;
// Map index to checkbox.
// TODO(lm) Would be better to stick the checkboxes into a vector upfront
switch (function_index)
{
case 0:
ui->invertCheckBox->setChecked(rcRev[rc_input_index]);
ui->rollWidget->setName(tr("Roll (#%1)").arg(rcMapping[0] + 1));
break;
case 1:
ui->invertCheckBox_2->setChecked(rcRev[rc_input_index]);
ui->pitchWidget->setName(tr("Pitch (#%1)").arg(rcMapping[1] + 1));
break;
case 2:
ui->invertCheckBox_3->setChecked(rcRev[rc_input_index]);
ui->yawWidget->setName(tr("Yaw (#%1)").arg(rcMapping[2] + 1));
break;
case 3:
ui->invertCheckBox_4->setChecked(rcRev[rc_input_index]);
ui->throttleWidget->setName(tr("Throt. (#%1)").arg(rcMapping[3] + 1));
break;
case 4:
ui->invertCheckBox_5->setChecked(rcRev[rc_input_index]);
//ui->radio5Widget->setName(tr("Mode Switch (#%1)").arg(rcMapping[4] + 1));
break;
case 5:
ui->assistSwInvertCheckBox->setChecked(rcRev[rc_input_index]);
break;
case 6:
ui->missionSwInvertCheckBox->setChecked(rcRev[rc_input_index]);
break;
case 7:
ui->returnSwInvertCheckBox->setChecked(rcRev[rc_input_index]);
break;
case 8:
ui->flapsInvertCheckBox->setChecked(rcRev[rc_input_index]);
break;
case 9:
ui->aux1InvertCheckBox->setChecked(rcRev[rc_input_index]);
break;
case 10:
ui->aux2InvertCheckBox->setChecked(rcRev[rc_input_index]);
break;
}
}
}
void QGCPX4VehicleConfig::updateMappingView(int function_index)
{
Q_UNUSED(function_index);
updateAllInvertedCheckboxes();
QStringList assignments;
for (unsigned i = 0; i < chanMax; i++) {
assignments << "";
}
for (unsigned i = 0; i < chanMappedMax; i++) {
if (rcMapping[i] >= 0 && rcMapping[i] < (int)chanMax) {
assignments.replace(rcMapping[i], assignments[rcMapping[i]].append(QString(" / ").append(channelNames[i])));
}
}
for (unsigned i = 0; i < chanMax; i++) {
if (assignments[i] == "")
assignments[i] = "UNUSED";
}
for (unsigned i = 0; i < chanMax; i++) {
switch (i) {
case 4:
ui->radio5Widget->setName(tr("%1 (#5)").arg(assignments[4]));
break;
case 5:
ui->radio6Widget->setName(tr("%1 (#6)").arg(assignments[5]));
break;
case 6:
ui->radio7Widget->setName(tr("%1 (#7)").arg(assignments[6]));
break;
case 7:
ui->radio8Widget->setName(tr("%1 (#8)").arg(assignments[7]));
break;
}
}
}
void QGCPX4VehicleConfig::handleRcParameterChange(QString parameterName, QVariant value)
{
if (parameterName.startsWith("RC_")) {
if (parameterName.startsWith("RC_MAP_")) {
//RC Mapping radio channels to meaning
// Order is: roll, pitch, yaw, throttle, mode sw, aux 1-3
int intValue = value.toInt() - 1;
if (parameterName.startsWith("RC_MAP_ROLL")) {
setChannelToFunctionMapping(0, intValue);
ui->rollSpinBox->setValue(rcMapping[0]+1);
ui->rollSpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_PITCH")) {
setChannelToFunctionMapping(1, intValue);
ui->pitchSpinBox->setValue(rcMapping[1]+1);
ui->pitchSpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_YAW")) {
setChannelToFunctionMapping(2, intValue);
ui->yawSpinBox->setValue(rcMapping[2]+1);
ui->yawSpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_THROTTLE")) {
setChannelToFunctionMapping(3, intValue);
ui->throttleSpinBox->setValue(rcMapping[3]+1);
ui->throttleSpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_MODE_SW")) {
setChannelToFunctionMapping(4, intValue);
ui->modeSpinBox->setValue(rcMapping[4]+1);
ui->modeSpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_ASSIST_SW")) {
setChannelToFunctionMapping(5, intValue);
ui->assistSwSpinBox->setValue(rcMapping[5]+1);
ui->assistSwSpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_MISSIO_SW")) {
setChannelToFunctionMapping(6, intValue);
ui->missionSwSpinBox->setValue(rcMapping[6]+1);
ui->missionSwSpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_RETURN_SW")) {
setChannelToFunctionMapping(7, intValue);
ui->returnSwSpinBox->setValue(rcMapping[7]+1);
ui->returnSwSpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_FLAPS")) {
setChannelToFunctionMapping(8, intValue);
ui->flapsSpinBox->setValue(rcMapping[8]+1);
ui->flapsSpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_AUX1")) {
setChannelToFunctionMapping(9, intValue);
ui->aux1SpinBox->setValue(rcMapping[9]+1);
ui->aux1SpinBox->setEnabled(true);
}
else if (parameterName.startsWith("RC_MAP_AUX2")) {
setChannelToFunctionMapping(10, intValue);
ui->aux2SpinBox->setValue(rcMapping[10]+1);
ui->aux2SpinBox->setEnabled(true);
}
}
else if (parameterName.startsWith("RC_SCALE_")) {
// Scaling
float floatVal = value.toFloat();
if (parameterName.startsWith("RC_SCALE_ROLL")) {
rcScaling[0] = floatVal;
}
else if (parameterName.startsWith("RC_SCALE_PITCH")) {
rcScaling[1] = floatVal;
}
else if (parameterName.startsWith("RC_SCALE_YAW")) {
rcScaling[2] = floatVal;
}
// Not implemented at this point
// else if (parameterName.startsWith("RC_SCALE_THROTTLE")) {
// rcScaling[3] = floatVal;
// }
// else if (parameterName.startsWith("RC_SCALE_AUX1")) {
// rcScaling[5] = floatVal;
// }
// else if (parameterName.startsWith("RC_SCALE_AUX2")) {
// rcScaling[6] = floatVal;
// }
}
}
else {
// Channel calibration values
bool ok = false;
unsigned int index = chanMax;
QRegExp minTpl("RC?_MIN");
minTpl.setPatternSyntax(QRegExp::Wildcard);
QRegExp maxTpl("RC?_MAX");
maxTpl.setPatternSyntax(QRegExp::Wildcard);
QRegExp trimTpl("RC?_TRIM");
trimTpl.setPatternSyntax(QRegExp::Wildcard);
QRegExp revTpl("RC?_REV");
revTpl.setPatternSyntax(QRegExp::Wildcard);
// Do not write the RC type, as these values depend on this
// active onboard parameter
int intVal = value.toInt();
if (minTpl.exactMatch(parameterName)) {
index = parameterName.mid(2, 1).toInt(&ok) - 1;
if (ok && index < chanMax) {
rcMin[index] = intVal;
updateRcWidgetValues();
}
}
else if (maxTpl.exactMatch(parameterName)) {
index = parameterName.mid(2, 1).toInt(&ok) - 1;
if (ok && index < chanMax) {
rcMax[index] = intVal;
updateRcWidgetValues();
}
}
else if (trimTpl.exactMatch(parameterName)) {
index = parameterName.mid(2, 1).toInt(&ok) - 1;
if (ok && index < chanMax) {
rcTrim[index] = intVal;
}
}
else if (revTpl.exactMatch(parameterName)) {
index = parameterName.mid(2, 1).toInt(&ok) - 1;
if (ok && index < chanMax) {
rcRev[index] = (intVal == -1) ? true : false;
for (unsigned i = 0; i < chanMappedMax; i++)
{
if (rcMapping[i] == (int)index)
updateMappingView(i);
}
}
}
}
}
void QGCPX4VehicleConfig::setChannelToFunctionMapping(int function, int channel)
{
if (function >= 0 && function < (int)chanMappedMax)
rcMapping[function] = channel;
if (channel >= 0 && channel < (int)chanMax)
rcToFunctionMapping[channel] = function;
}
void QGCPX4VehicleConfig::parameterChanged(int uas, int component, QString parameterName, QVariant value)
{
if (!doneLoadingConfig) {
//We do not want to attempt to generate any UI elements until loading of the config file is complete.
//We should re-request params later if needed, that is not implemented yet.
return;
}
//TODO this may introduce a bug with param editor widgets not receiving param updates
if (parameterName.startsWith("RC")) {
handleRcParameterChange(parameterName,value);
return;
}
if (paramToWidgetMap.contains(parameterName)) {
//Main group of parameters of the selected airframe
paramToWidgetMap.value(parameterName)->setParameterValue(uas,component,parameterName,value);
if (toolToBoxMap.contains(paramToWidgetMap.value(parameterName))) {
toolToBoxMap[paramToWidgetMap.value(parameterName)]->show();
}
else {
qCritical() << "Widget with no box, possible memory corruption for param:" << parameterName;
}
}
else if (libParamToWidgetMap.contains(parameterName)) {
//All the library parameters
libParamToWidgetMap.value(parameterName)->setParameterValue(uas,component,parameterName,value);
if (toolToBoxMap.contains(libParamToWidgetMap.value(parameterName))) {
toolToBoxMap[libParamToWidgetMap.value(parameterName)]->show();
}
else {
qCritical() << "Widget with no box, possible memory corruption for param:" << parameterName;
}
}
else {
//Param recieved that we have no metadata for. Search to see if it belongs in a
//group with some other params
bool found = false;
for (int i=0;i<toolWidgets.size();i++) {
if (parameterName.startsWith(toolWidgets[i]->objectName())) {
//It should be grouped with this one, add it.
toolWidgets[i]->addParam(uas,component,parameterName,value);
libParamToWidgetMap.insert(parameterName,toolWidgets[i]);
found = true;
break;
}
}
// if (!found) {
// //New param type, create a QGroupBox for it.
// QWidget* parent = ui->advanceColumnContents;
// // Create the tool, attaching it to the QGroupBox
// QGCToolWidget *tool = new QGCToolWidget("", parent);
// QString tooltitle = parameterName;
// if (parameterName.split("_").size() > 1) {
// tooltitle = parameterName.split("_")[0] + "_";
// }
// tool->setTitle(tooltitle);
// tool->setObjectName(tooltitle);
// //tool->setSettings(set);
// libParamToWidgetMap.insert(parameterName,tool);
// toolWidgets.append(tool);
// tool->addParam(uas, component, parameterName, value);
// QGroupBox *box = new QGroupBox(parent);
// box->setTitle(tool->objectName());
// box->setLayout(new QVBoxLayout(box));
// box->layout()->addWidget(tool);
// libParamToWidgetMap.insert(parameterName,tool);
// toolWidgets.append(tool);
// ui->advancedColumnLayout->addWidget(box);
// toolToBoxMap[tool] = box;
// }
}
}
void QGCPX4VehicleConfig::updateStatus(const QString& str)
{
ui->advancedStatusLabel->setText(str);
ui->advancedStatusLabel->setStyleSheet("");
}
void QGCPX4VehicleConfig::updateError(const QString& str)
{
ui->advancedStatusLabel->setText(str);
ui->advancedStatusLabel->setStyleSheet(QString("QLabel { margin: 0px 2px; font: 14px; color: %1; background-color: %2; }").arg(QGC::colorDarkWhite.name()).arg(QGC::colorMagenta.name()));
}
void QGCPX4VehicleConfig::checktimeOuts()
{
}
void QGCPX4VehicleConfig::updateRcWidgetValues()
{
ui->rollWidget->setValueAndRange(rcMappedValue[0],rcMappedMin[0],rcMappedMax[0]);
ui->pitchWidget->setValueAndRange(rcMappedValue[1],rcMappedMin[1],rcMappedMax[1]);
ui->yawWidget->setValueAndRange(rcMappedValue[2],rcMappedMin[2],rcMappedMax[2]);
ui->throttleWidget->setValueAndRange(rcMappedValue[3],rcMappedMin[3],rcMappedMax[3]);
ui->radio5Widget->setValueAndRange(rcValueReversed[4],rcMin[4],rcMax[4]);
ui->radio6Widget->setValueAndRange(rcValueReversed[5],rcMin[5],rcMax[5]);
ui->radio7Widget->setValueAndRange(rcValueReversed[6],rcMin[6],rcMax[6]);
ui->radio8Widget->setValueAndRange(rcValueReversed[7],rcMin[7],rcMax[7]);
}
void QGCPX4VehicleConfig::updateRcChanLabels()
{
ui->rollChanLabel->setText(labelForRcValue(rcRoll));
ui->pitchChanLabel->setText(labelForRcValue(rcPitch));
ui->yawChanLabel->setText(labelForRcValue(rcYaw));
ui->throttleChanLabel->setText(labelForRcValue(rcThrottle));
QString blankLabel = tr("---");
if (rcValue[rcMapping[4]] != UINT16_MAX) {
ui->modeChanLabel->setText(labelForRcValue(rcMode));
}
else {
ui->modeChanLabel->setText(blankLabel);
}
if (rcValue[rcMapping[5]] != UINT16_MAX) {
ui->assistSwChanLabel->setText(labelForRcValue(rcAssist));
}
else {
ui->assistSwChanLabel->setText(blankLabel);
}
if (rcValue[rcMapping[6]] != UINT16_MAX) {
ui->missionSwChanLabel->setText(labelForRcValue(rcMission));
}
else {
ui->missionSwChanLabel->setText(blankLabel);
}
if (rcValue[rcMapping[7]] != UINT16_MAX) {
ui->returnSwChanLabel->setText(labelForRcValue(rcReturn));
}
else {
ui->returnSwChanLabel->setText(blankLabel);
}
if (rcValue[rcMapping[8]] != UINT16_MAX) {
ui->flapsChanLabel->setText(labelForRcValue(rcFlaps));
}
else {
ui->flapsChanLabel->setText(blankLabel);
}
if (rcValue[rcMapping[9]] != UINT16_MAX) {
ui->aux1ChanLabel->setText(labelForRcValue(rcAux1));
}
else {
ui->aux1ChanLabel->setText(blankLabel);
}
if (rcValue[rcMapping[10]] != UINT16_MAX) {
ui->aux2ChanLabel->setText(labelForRcValue(rcAux2));
}
else {
ui->aux2ChanLabel->setText(blankLabel);
}
}
void QGCPX4VehicleConfig::updateView()
{
if (dataModelChanged) {
dataModelChanged = false;
updateRcWidgetValues();
updateRcChanLabels();
if (chanCount > 0)
ui->rcLabel->setText(tr("Radio control detected with %1 channels.").arg(chanCount));
}
}