Commit 6d7fb36a authored by Don Gagne's avatar Don Gagne

New mavlink logging support

- Logging now always on to temp file
- User prompted to save temp file after disconnect
- Orphaned temp logs checked for on boot
- Logs saved to new save file location
- Log file starts/stops with first and last connnection
parent 84fb1ad7
......@@ -30,9 +30,13 @@
#include "QGCMAVLink.h"
#include "QGCMAVLinkUASFactory.h"
#include "QGC.h"
#include "QGCCore.h"
Q_DECLARE_METATYPE(mavlink_message_t)
const char* MAVLinkProtocol::_tempLogFileTemplate = "FlightDataXXXXXX"; ///< Template for temporary log file
const char* MAVLinkProtocol::_logFileExtension = "mavlink"; ///< Extension for log files
/**
* The default constructor will create a new MAVLink object sending heartbeats at
* the MAVLINK_HEARTBEAT_DEFAULT_RATE to all connected links.
......@@ -43,8 +47,6 @@ MAVLinkProtocol::MAVLinkProtocol() :
m_heartbeatsEnabled(true),
m_multiplexingEnabled(false),
m_authEnabled(false),
m_loggingEnabled(false),
m_logfile(NULL),
m_enable_version_check(true),
m_paramRetransmissionTimeout(350),
m_paramRewriteTimeout(500),
......@@ -53,9 +55,17 @@ MAVLinkProtocol::MAVLinkProtocol() :
m_actionRetransmissionTimeout(100),
versionMismatchIgnore(false),
systemId(QGC::defaultSystemId),
_should_exit(false)
_should_exit(false),
_logSuspendError(false),
_logSuspendReplay(false),
_protocolStatusMessageConnected(false),
_saveTempFlightDataLogConnected(false)
{
qRegisterMetaType<mavlink_message_t>("mavlink_message_t");
_tempLogFile.setFileTemplate(QString("%1/%2.%3").arg(QStandardPaths::writableLocation(QStandardPaths::TempLocation)).arg(_tempLogFileTemplate).arg(_logFileExtension));
_tempLogFile.setAutoRemove(false);
m_authKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
loadSettings();
......@@ -88,18 +98,6 @@ void MAVLinkProtocol::loadSettings()
enableVersionCheck(settings.value("VERSION_CHECK_ENABLED", m_enable_version_check).toBool());
enableMultiplexing(settings.value("MULTIPLEXING_ENABLED", m_multiplexingEnabled).toBool());
// Only set logfile if there is a name present in settings
if (settings.contains("LOGFILE_NAME") && m_logfile == NULL)
{
m_logfile = new QFile(settings.value("LOGFILE_NAME").toString());
}
else if (m_logfile == NULL)
{
m_logfile = new QFile(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/qgroundcontrol_packetlog.mavlink");
}
// Enable logging
enableLogging(settings.value("LOGGING_ENABLED", m_loggingEnabled).toBool());
// Only set system id if it was valid
int temp = settings.value("GCS_SYSTEM_ID", systemId).toInt();
if (temp > 0 && temp < 256)
......@@ -127,17 +125,11 @@ void MAVLinkProtocol::storeSettings()
QSettings settings;
settings.beginGroup("QGC_MAVLINK_PROTOCOL");
settings.setValue("HEARTBEATS_ENABLED", m_heartbeatsEnabled);
settings.setValue("LOGGING_ENABLED", m_loggingEnabled);
settings.setValue("VERSION_CHECK_ENABLED", m_enable_version_check);
settings.setValue("MULTIPLEXING_ENABLED", m_multiplexingEnabled);
settings.setValue("GCS_SYSTEM_ID", systemId);
settings.setValue("GCS_AUTH_KEY", m_authKey);
settings.setValue("GCS_AUTH_ENABLED", m_authEnabled);
if (m_logfile)
{
// Logfile exists, store the name
settings.setValue("LOGFILE_NAME", m_logfile->fileName());
}
// Parameter interface settings
settings.setValue("PARAMETER_RETRANSMISSION_TIMEOUT", m_paramRetransmissionTimeout);
settings.setValue("PARAMETER_REWRITE_TIMEOUT", m_paramRewriteTimeout);
......@@ -150,16 +142,8 @@ void MAVLinkProtocol::storeSettings()
MAVLinkProtocol::~MAVLinkProtocol()
{
storeSettings();
if (m_logfile)
{
if (m_logfile->isOpen())
{
m_logfile->flush();
m_logfile->close();
}
delete m_logfile;
m_logfile = NULL;
}
_closeLogFile();
// Tell the thread to exit
_should_exit = true;
......@@ -192,18 +176,6 @@ void MAVLinkProtocol::run()
}
}
QString MAVLinkProtocol::getLogfileName()
{
if (m_logfile)
{
return m_logfile->fileName();
}
else
{
return QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/qgroundcontrol_packetlog.mavlink";
}
}
void MAVLinkProtocol::resetMetadataForLink(const LinkInterface *link)
{
int linkId = link->getId();
......@@ -217,17 +189,47 @@ void MAVLinkProtocol::resetMetadataForLink(const LinkInterface *link)
void MAVLinkProtocol::linkStatusChanged(bool connected)
{
LinkInterface* link = qobject_cast<LinkInterface*>(QObject::sender());
if (link == NULL) {
Q_ASSERT(false);
return;
}
if (connected) {
Q_ASSERT(!_connectedLinks.contains(link));
_connectedLinks.append(link);
if (_connectedLinks.count() == 1) {
// This is the first link, we need to start logging
_startLogging();
}
// Send command to start MAVLink
// XXX hacky but safe
// Start NSH
const char init[] = {0x0d, 0x0d, 0x0d};
link->writeBytes(init, sizeof(init));
const char* cmd = "sh /etc/init.d/rc.usb\n";
link->writeBytes(cmd, strlen(cmd));
link->writeBytes(init, 4);
} else {
Q_ASSERT(_connectedLinks.contains(link));
_connectedLinks.removeOne(link);
if (_connectedLinks.count() == 0) {
// Last link is gone, close out logging
_stopLogging();
}
}
// Track the links which are connected to the protocol
QList<LinkInterface*> _connectedLinks; ///< List of all links connected to protocol
qDebug() << "linkStatusChanged" << connected;
if (link) {
if (connected) {
// Send command to start MAVLink
// XXX hacky but safe
// Start NSH
const char init[] = {0x0d, 0x0d, 0x0d};
link->writeBytes(init, sizeof(init));
const char* cmd = "sh /etc/init.d/rc.usb\n";
link->writeBytes(cmd, strlen(cmd));
link->writeBytes(init, 4);
}
}
}
......@@ -314,8 +316,10 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
}
// Log data
if (m_loggingEnabled && m_logfile)
{
Q_ASSERT(_logSuspendError || _logSuspendReplay || _tempLogFile.isOpen());
if (!_logSuspendError && !_logSuspendReplay) {
uint8_t buf[MAVLINK_MAX_PACKET_LEN+sizeof(quint64)];
// Write the uint64 time in microseconds in big endian format before the message.
......@@ -332,11 +336,12 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
// Now write this timestamp/message pair to the log.
QByteArray b((const char*)buf, len);
if(m_logfile->write(b) != len)
if(_tempLogFile.write(b) != len)
{
// If there's an error logging data, raise an alert and stop logging.
emit protocolStatusMessage(tr("MAVLink Logging failed"), tr("Could not write to file %1, disabling logging.").arg(m_logfile->fileName()));
enableLogging(false);
emit protocolStatusMessage(tr("MAVLink Logging failed"), tr("Could not write to file %1, logging disabled.").arg(_tempLogFile.fileName()));
_stopLogging();
_logSuspendError = true;
}
}
......@@ -633,62 +638,6 @@ void MAVLinkProtocol::setActionRetransmissionTimeout(int ms)
}
}
void MAVLinkProtocol::enableLogging(bool enabled)
{
bool changed = false;
if (enabled != m_loggingEnabled) changed = true;
if (enabled)
{
if (m_logfile && m_logfile->isOpen())
{
m_logfile->flush();
m_logfile->close();
}
if (m_logfile)
{
if (!m_logfile->open(QIODevice::WriteOnly | QIODevice::Append))
{
emit protocolStatusMessage(tr("Opening MAVLink logfile for writing failed"), tr("MAVLink cannot log to the file %1, please choose a different file. Stopping logging.").arg(m_logfile->fileName()));
m_loggingEnabled = false;
}
}
else
{
emit protocolStatusMessage(tr("Opening MAVLink logfile for writing failed"), tr("MAVLink cannot start logging, no logfile selected."));
}
}
else if (!enabled)
{
if (m_logfile)
{
if (m_logfile->isOpen())
{
m_logfile->flush();
m_logfile->close();
}
}
}
m_loggingEnabled = enabled;
if (changed) emit loggingChanged(enabled);
}
void MAVLinkProtocol::setLogfileName(const QString& filename)
{
if (!m_logfile)
{
m_logfile = new QFile(filename);
}
else
{
m_logfile->flush();
m_logfile->close();
}
m_logfile->setFileName(filename);
enableLogging(m_loggingEnabled);
}
void MAVLinkProtocol::enableVersionCheck(bool enabled)
{
m_enable_version_check = enabled;
......@@ -711,3 +660,103 @@ int MAVLinkProtocol::getHeartbeatRate()
{
return heartbeatRate;
}
/// @brief Closes the log file if it is open
bool MAVLinkProtocol::_closeLogFile(void)
{
if (_tempLogFile.isOpen()) {
if (_tempLogFile.size() == 0) {
// Don't save zero byte files
_tempLogFile.remove();
return false;
} else {
_tempLogFile.flush();
_tempLogFile.close();
return true;
}
}
return false;
}
void MAVLinkProtocol::_startLogging(void)
{
Q_ASSERT(!_tempLogFile.isOpen());
if (!_logSuspendReplay) {
if (!_tempLogFile.open()) {
emit protocolStatusMessage(tr("Opening Flight Data file for writing failed"),
tr("Unable to write to %1. Please choose a different file location.").arg(_tempLogFile.fileName()));
_closeLogFile();
_logSuspendError = true;
return;
}
qDebug() << "Temp log" << _tempLogFile.fileName();
_logSuspendError = false;
}
}
void MAVLinkProtocol::_stopLogging(void)
{
if (_closeLogFile()) {
emit saveTempFlightDataLog(_tempLogFile.fileName());
}
}
/// @brief We override this virtual from QObject so that we can track when the
/// protocolStatusMessage and saveTempFlightDataLog signals are connected.
/// Once both are connected we can check for lost log files.
void MAVLinkProtocol::connectNotify(const QMetaMethod& signal)
{
if (signal == QMetaMethod::fromSignal(&MAVLinkProtocol::protocolStatusMessage)) {
_protocolStatusMessageConnected = true;
if (_saveTempFlightDataLogConnected) {
_checkLostLogFiles();
}
} else if (signal == QMetaMethod::fromSignal(&MAVLinkProtocol::saveTempFlightDataLog)) {
_saveTempFlightDataLogConnected = true;
if (_protocolStatusMessageConnected) {
_checkLostLogFiles();
}
}
}
/// @brief Checks the temp directory for log files which may have been left there.
/// This could happen if QGC crashes without the temp log file being saved.
/// Give the user an option to save these orphaned files.
void MAVLinkProtocol::_checkLostLogFiles(void)
{
QDir tempDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
QString filter(QString("*.%1").arg(_logFileExtension));
QFileInfoList fileInfoList = tempDir.entryInfoList(QStringList(filter), QDir::Files);
qDebug() << "Orphaned log file count" << fileInfoList.count();
foreach(const QFileInfo fileInfo, fileInfoList) {
qDebug() << "Orphaned log file" << fileInfo.filePath();
if (fileInfo.size() == 0) {
// Delete all zero length files
QFile::remove(fileInfo.filePath());
continue;
}
// Give the user a chance to save the orphaned log file
emit protocolStatusMessage(tr("Found unsaved Flight Data"),
tr("This can happen if QGroundControl crashes during Flight Data collection. "
"If you want to save the unsaved Flight Data, select the file you want to save it to. "
"If you do not want to keep the Flight Data, select 'Cancel' on the next dialog."));
emit saveTempFlightDataLog(fileInfo.filePath());
}
}
void MAVLinkProtocol::suspendLogForReplay(bool suspend)
{
// This must come through a slot so that we handle this on the right thread. This will
// also cause the signal to be queued behind any bytesReady signals which must be flushed
// out of the queue before we change suspend state.
Q_ASSERT(QThread::currentThread() == this);
_logSuspendReplay = suspend;
}
......@@ -37,6 +37,8 @@ This file is part of the QGROUNDCONTROL project
#include <QFile>
#include <QMap>
#include <QByteArray>
#include <QTemporaryFile>
#include "ProtocolInterface.h"
#include "LinkInterface.h"
#include "QGCMAVLink.h"
......@@ -69,10 +71,7 @@ public:
bool heartbeatsEnabled() const {
return m_heartbeatsEnabled;
}
/** @brief Get logging state */
bool loggingEnabled() const {
return m_loggingEnabled;
}
/** @brief Get protocol version check state */
bool versionCheckEnabled() const {
return m_enable_version_check;
......@@ -93,8 +92,6 @@ public:
QString getAuthKey() {
return m_authKey;
}
/** @brief Get the name of the packet log file */
QString getLogfileName();
/** @brief Get state of parameter retransmission */
bool paramGuardEnabled() {
return m_paramGuardEnabled;
......@@ -140,7 +137,7 @@ public:
* Reset the counters for all metadata for this link.
*/
virtual void resetMetadataForLink(const LinkInterface *link);
void run();
public slots:
......@@ -161,9 +158,6 @@ public slots:
/** @brief Enable / disable the heartbeat emission */
void enableHeartbeats(bool enabled);
/** @brief Enable/disable binary packet logging */
void enableLogging(bool enabled);
/** @brief Enabled/disable packet multiplexing */
void enableMultiplexing(bool enabled);
......@@ -182,9 +176,6 @@ public slots:
/** @brief Set parameter read timeout */
void setActionRetransmissionTimeout(int ms);
/** @brief Set log file name */
void setLogfileName(const QString& filename);
/** @brief Enable / disable version check */
void enableVersionCheck(bool enabled);
......@@ -203,16 +194,22 @@ public slots:
void loadSettings();
/** @brief Store protocol settings */
void storeSettings();
/// @brief Suspend/Restart logging during replay. This must be emitted as a signal
/// and not called directly in order to synchronize with the bytesReady signal
/// which may be ahead of it in the signal queue.
void suspendLogForReplay(bool suspend);
protected:
// Override from QObject
virtual void connectNotify(const QMetaMethod& signal);
protected:
QTimer *heartbeatTimer; ///< Timer to emit heartbeats
int heartbeatRate; ///< Heartbeat rate, controls the timer interval
bool m_heartbeatsEnabled; ///< Enabled/disable heartbeat emission
bool m_multiplexingEnabled; ///< Enable/disable packet multiplexing
bool m_authEnabled; ///< Enable authentication token broadcast
QString m_authKey; ///< Authentication key
bool m_loggingEnabled; ///< Enable/disable packet logging
QFile* m_logfile; ///< Logfile
bool m_enable_version_check; ///< Enable checking of version match of MAV and QGC
int m_paramRetransmissionTimeout; ///< Timeout for parameter retransmission
int m_paramRewriteTimeout; ///< Timeout for sending re-write request
......@@ -235,8 +232,6 @@ signals:
void messageReceived(LinkInterface* link, mavlink_message_t message);
/** @brief Emitted if heartbeat emission mode is changed */
void heartbeatChanged(bool heartbeats);
/** @brief Emitted if logging is started / stopped */
void loggingChanged(bool enabled);
/** @brief Emitted if multiplexing is started / stopped */
void multiplexingChanged(bool enabled);
/** @brief Emitted if authentication support is enabled / disabled */
......@@ -272,6 +267,27 @@ signals:
*/
void radioStatusChanged(LinkInterface* link, unsigned rxerrors, unsigned fixed, unsigned rssi, unsigned remrssi,
unsigned txbuf, unsigned noise, unsigned remnoise);
/// @brief Emitted when a temporary log file is ready for saving
void saveTempFlightDataLog(QString tempLogfile);
private:
bool _closeLogFile(void);
void _startLogging(void);
void _stopLogging(void);
void _checkLostLogFiles(void);
QList<LinkInterface*> _connectedLinks; ///< List of all links connected to protocol
bool _logSuspendError; ///< true: Logging suspended due to error
bool _logSuspendReplay; ///< true: Logging suspended due to replay
QTemporaryFile _tempLogFile; ///< File to log to
static const char* _tempLogFileTemplate; ///< Template for temporary log file
static const char* _logFileExtension; ///< Extension for log files
bool _protocolStatusMessageConnected; ///< true: protocolStatusMessage signal has been connected
bool _saveTempFlightDataLogConnected; ///< true: saveTempFlightDataLog signal has been connected
};
#endif // MAVLINKPROTOCOL_H_
......@@ -55,7 +55,6 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget
// Initialize state
m_ui->heartbeatCheckBox->setChecked(protocol->heartbeatsEnabled());
m_ui->loggingCheckBox->setChecked(protocol->loggingEnabled());
m_ui->versionCheckBox->setChecked(protocol->versionCheckEnabled());
m_ui->multiplexingCheckBox->setChecked(protocol->multiplexingEnabled());
m_ui->systemIdSpinBox->setValue(protocol->getSystemId());
......@@ -71,14 +70,9 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget
// Heartbeat
connect(protocol, SIGNAL(heartbeatChanged(bool)), m_ui->heartbeatCheckBox, SLOT(setChecked(bool)));
connect(m_ui->heartbeatCheckBox, SIGNAL(toggled(bool)), protocol, SLOT(enableHeartbeats(bool)));
// Logging
connect(protocol, SIGNAL(loggingChanged(bool)), m_ui->loggingCheckBox, SLOT(setChecked(bool)));
connect(m_ui->loggingCheckBox, SIGNAL(toggled(bool)), protocol, SLOT(enableLogging(bool)));
// Version check
connect(protocol, SIGNAL(versionCheckChanged(bool)), m_ui->versionCheckBox, SLOT(setChecked(bool)));
connect(m_ui->versionCheckBox, SIGNAL(toggled(bool)), protocol, SLOT(enableVersionCheck(bool)));
// Logfile
connect(m_ui->logFileButton, SIGNAL(clicked()), this, SLOT(chooseLogfileName()));
// System ID
connect(protocol, SIGNAL(systemIdChanged(int)), m_ui->systemIdSpinBox, SLOT(setValue(int)));
connect(m_ui->systemIdSpinBox, SIGNAL(valueChanged(int)), protocol, SLOT(setSystemId(int)));
......@@ -110,16 +104,10 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget
// Update values
m_ui->versionLabel->setText(tr("MAVLINK_VERSION: %1").arg(protocol->getVersion()));
updateLogfileName(protocol->getLogfileName());
// Connect visibility updates
connect(protocol, SIGNAL(versionCheckChanged(bool)), m_ui->versionLabel, SLOT(setVisible(bool)));
m_ui->versionLabel->setVisible(protocol->versionCheckEnabled());
// Logging visibility
connect(protocol, SIGNAL(loggingChanged(bool)), m_ui->logFileLabel, SLOT(setVisible(bool)));
m_ui->logFileLabel->setVisible(protocol->loggingEnabled());
connect(protocol, SIGNAL(loggingChanged(bool)), m_ui->logFileButton, SLOT(setVisible(bool)));
m_ui->logFileButton->setVisible(protocol->loggingEnabled());
// // Multiplexing visibility
// connect(protocol, SIGNAL(multiplexingChanged(bool)), m_ui->multiplexingFilterCheckBox, SLOT(setVisible(bool)));
// m_ui->multiplexingFilterCheckBox->setVisible(protocol->multiplexingEnabled());
......@@ -146,39 +134,6 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget
m_ui->multiplexingFilterLineEdit->setVisible(false);
}
void MAVLinkSettingsWidget::updateLogfileName(const QString& fileName)
{
QFileInfo file(fileName);
m_ui->logFileLabel->setText(file.fileName());
}
void MAVLinkSettingsWidget::chooseLogfileName()
{
QString fileName = QFileDialog::getSaveFileName(this, tr("Specify MAVLink log file name"), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation), tr("MAVLink Logfile (*.mavlink);;"));
if (!fileName.endsWith(".mavlink"))
{
fileName.append(".mavlink");
}
QFileInfo file(fileName);
if (file.exists() && !file.isWritable())
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText(tr("The selected logfile is not writable"));
msgBox.setInformativeText(tr("Please make sure that the file %1 is writable or select a different file").arg(fileName));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
else
{
updateLogfileName(fileName);
protocol->setLogfileName(fileName);
}
}
void MAVLinkSettingsWidget::enableDroneOS(bool enable)
{
// Enable multiplexing
......
......@@ -18,10 +18,6 @@ public:
~MAVLinkSettingsWidget();
public slots:
/** @brief Update the log file name display */
void updateLogfileName(const QString& fileName);
/** @brief Start the file select dialog for the log file */
void chooseLogfileName();
/** @brief Enable DroneOS forwarding */
void enableDroneOS(bool enable);
......
......@@ -7,35 +7,18 @@
<x>0</x>
<y>0</y>
<width>431</width>
<height>530</height>
<height>442</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="1,100,1">
<item row="1" column="0" colspan="3">
<widget class="QCheckBox" name="heartbeatCheckBox">
<property name="text">
<string>Emit heartbeat</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="3">
<widget class="QCheckBox" name="loggingCheckBox">
<property name="text">
<string>Log all MAVLink packets</string>
</property>
</widget>
</item>
<item row="8" column="0">
<spacer name="logFileSpacer">
<layout class="QGridLayout" name="gridLayout" columnstretch="1,0,0">
<item row="16" column="0">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
......@@ -44,15 +27,8 @@
</property>
</spacer>
</item>
<item row="9" column="0" colspan="3">
<widget class="QCheckBox" name="versionCheckBox">
<property name="text">
<string>Only accept MAVs with same protocol version</string>
</property>
</widget>
</item>
<item row="10" column="0">
<spacer name="versionSpacer">
<item row="13" column="0">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
......@@ -64,72 +40,28 @@
</property>
</spacer>
</item>
<item row="10" column="1" colspan="2">
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>MAVLINK_VERSION: </string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="logFileLabel">
<property name="text">
<string>Logfile name</string>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QPushButton" name="logFileButton">
<property name="text">
<string>Select..</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="systemIdSpinBox">
<item row="11" column="2">
<widget class="QSpinBox" name="paramRewriteSpinBox">
<property name="toolTip">
<string>Set the groundstation number</string>
<string>Time in milliseconds after which a not acknowledged write request is sent again.</string>
</property>
<property name="statusTip">
<string>Set the groundstation number</string>
<string>Time in milliseconds after which a not acknowledged write request is sent again.</string>
</property>
<property name="suffix">
<string> ms</string>
</property>
<property name="minimum">
<number>1</number>
<number>50</number>
</property>
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="systemIdLabel">
<property name="toolTip">
<string>The system ID is the number the MAV associates with this computer</string>
</property>
<property name="statusTip">
<string>The system ID is the number the MAV associates with this computer</string>
<number>60000</number>
</property>
<property name="text">
<string>Groundstation MAVLink System ID</string>
<property name="singleStep">
<number>50</number>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="multiplexingFilterSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="1" colspan="2">
<widget class="QLineEdit" name="multiplexingFilterLineEdit">
<property name="text">
......@@ -137,28 +69,23 @@
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QCheckBox" name="multiplexingFilterCheckBox">
<property name="text">
<string>Filter multiplexed packets: Only forward selected IDs</string>
<item row="13" column="2">
<widget class="QSpinBox" name="actionRetransmissionSpinBox">
<property name="suffix">
<string> ms</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QCheckBox" name="multiplexingCheckBox">
<property name="text">
<string>Enable Multiplexing: Forward packets to all other links</string>
<property name="minimum">
<number>20</number>
</property>
</widget>
</item>
<item row="11" column="0" colspan="3">
<widget class="QCheckBox" name="paramGuardCheckBox">
<property name="text">
<string>Enable retransmission of parameter read/write requests</string>
<property name="maximum">
<number>1000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
</widget>
</item>
<item row="12" column="0">
<item row="10" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
......@@ -171,34 +98,7 @@
</property>
</spacer>
</item>
<item row="13" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="12" column="1">
<widget class="QLabel" name="paramRetransmissionLabel">
<property name="text">
<string>Read request retransmission timeout</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QLabel" name="paramRewriteLabel">
<property name="text">
<string>Write request retransmission timeout</string>
</property>
</widget>
</item>
<item row="12" column="2">
<item row="10" column="2">
<widget class="QSpinBox" name="paramRetransmissionSpinBox">
<property name="toolTip">
<string>Time in milliseconds after which a not acknowledged read request is sent again.</string>
......@@ -223,37 +123,41 @@
</property>
</widget>
</item>
<item row="13" column="2">
<widget class="QSpinBox" name="paramRewriteSpinBox">
<property name="toolTip">
<string>Time in milliseconds after which a not acknowledged write request is sent again.</string>
</property>
<property name="statusTip">
<string>Time in milliseconds after which a not acknowledged write request is sent again.</string>
</property>
<property name="suffix">
<string> ms</string>
</property>
<property name="minimum">
<number>50</number>
<item row="14" column="0" colspan="3">
<widget class="Line" name="line">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximum">
<number>60000</number>
<property name="midLineWidth">
<number>0</number>
</property>
<property name="singleStep">
<number>50</number>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="14" column="0" colspan="3">
<widget class="QCheckBox" name="actionGuardCheckBox">
<property name="text">
<string>Enable retransmission of actions / commands</string>
<item row="17" column="1" colspan="2">
<widget class="QComboBox" name="droneOSComboBox">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>mavlink.droneos.com:14555</string>
</property>
</item>
<item>
<property name="text">
<string>localhost:14555</string>
</property>
</item>
</widget>
</item>
<item row="15" column="0">
<spacer name="horizontalSpacer_3">
<item row="11" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
......@@ -265,57 +169,106 @@
</property>
</spacer>
</item>
<item row="15" column="1">
<widget class="QLabel" name="actionRetransmissionLabel">
<item row="11" column="1">
<widget class="QLabel" name="paramRewriteLabel">
<property name="text">
<string>Action request retransmission timeout</string>
<string>Write request retransmission timeout</string>
</property>
</widget>
</item>
<item row="15" column="2">
<widget class="QSpinBox" name="actionRetransmissionSpinBox">
<property name="suffix">
<string> ms</string>
<item row="10" column="1">
<widget class="QLabel" name="paramRetransmissionLabel">
<property name="text">
<string>Read request retransmission timeout</string>
</property>
<property name="minimum">
<number>20</number>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QCheckBox" name="heartbeatCheckBox">
<property name="text">
<string>Emit heartbeat</string>
</property>
<property name="maximum">
<number>1000</number>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QCheckBox" name="multiplexingCheckBox">
<property name="text">
<string>Enable Multiplexing: Forward packets to all other links</string>
</property>
<property name="singleStep">
<number>10</number>
</widget>
</item>
<item row="13" column="1">
<widget class="QLabel" name="actionRetransmissionLabel">
<property name="text">
<string>Action request retransmission timeout</string>
</property>
</widget>
</item>
<item row="16" column="0" colspan="3">
<widget class="Line" name="line">
<property name="minimumSize">
<item row="8" column="0">
<spacer name="versionSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<width>8</width>
<height>0</height>
</size>
</property>
<property name="midLineWidth">
<number>0</number>
</spacer>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="systemIdSpinBox">
<property name="toolTip">
<string>Set the groundstation number</string>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<property name="statusTip">
<string>Set the groundstation number</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="17" column="0" colspan="3">
<item row="15" column="0" colspan="3">
<widget class="QCheckBox" name="droneOSCheckBox">
<property name="text">
<string>Forward MAVLink packets of all links to http://droneos.com</string>
</property>
</widget>
</item>
<item row="18" column="0">
<spacer name="horizontalSpacer_4">
<item row="12" column="0" colspan="3">
<widget class="QCheckBox" name="actionGuardCheckBox">
<property name="text">
<string>Enable retransmission of actions / commands</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QCheckBox" name="multiplexingFilterCheckBox">
<property name="text">
<string>Filter multiplexed packets: Only forward selected IDs</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="3">
<widget class="QCheckBox" name="versionCheckBox">
<property name="text">
<string>Only accept MAVs with same protocol version</string>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="multiplexingFilterSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>8</width>
......@@ -324,7 +277,7 @@
</property>
</spacer>
</item>
<item row="18" column="1" colspan="2">
<item row="16" column="1" colspan="2">
<widget class="QLineEdit" name="droneOSLineEdit">
<property name="text">
<string>Enter your DroneOS API Token/Key</string>
......@@ -334,21 +287,31 @@
</property>
</widget>
</item>
<item row="19" column="1" colspan="2">
<widget class="QComboBox" name="droneOSComboBox">
<property name="editable">
<bool>true</bool>
<item row="9" column="0" colspan="3">
<widget class="QCheckBox" name="paramGuardCheckBox">
<property name="text">
<string>Enable retransmission of parameter read/write requests</string>
</property>
</widget>
</item>
<item row="8" column="1" colspan="2">
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>MAVLINK_VERSION: </string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="systemIdLabel">
<property name="toolTip">
<string>The system ID is the number the MAV associates with this computer</string>
</property>
<property name="statusTip">
<string>The system ID is the number the MAV associates with this computer</string>
</property>
<property name="text">
<string>Groundstation MAVLink System ID</string>
</property>
<item>
<property name="text">
<string>mavlink.droneos.com:14555</string>
</property>
</item>
<item>
<property name="text">
<string>localhost:14555</string>
</property>
</item>
</widget>
</item>
</layout>
......
......@@ -70,6 +70,7 @@ This file is part of the QGROUNDCONTROL project
#include "menuactionhelper.h"
#include "QGCUASFileViewMulti.h"
#include <QDesktopWidget>
#include "QGCCore.h"
#ifdef QGC_OSG_ENABLED
#include "Q3DWidgetFactory.h"
......@@ -80,41 +81,38 @@ This file is part of the QGROUNDCONTROL project
#include "LogCompressor.h"
static MainWindow* _instance = NULL; ///< @brief MainWindow singleton
// Set up some constants
const QString MainWindow::defaultDarkStyle = ":files/styles/style-dark.css";
const QString MainWindow::defaultLightStyle = ":files/styles/style-light.css";
MainWindow* MainWindow::instance_mode(QSplashScreen* screen, enum MainWindow::CUSTOM_MODE mode)
MainWindow* MainWindow::_create(QSplashScreen* splashScreen, enum MainWindow::CUSTOM_MODE mode)
{
static MainWindow* _instance = 0;
if (_instance == 0)
{
_instance = new MainWindow();
_instance->setCustomMode(mode);
if (screen)
{
connect(_instance, SIGNAL(initStatusChanged(QString,int,QColor)),
screen, SLOT(showMessage(QString,int,QColor)));
}
_instance->init();
}
Q_ASSERT(_instance == NULL);
Q_ASSERT(splashScreen);
new MainWindow(splashScreen, mode);
// _instance is set in constructor
Q_ASSERT(_instance);
return _instance;
}
MainWindow* MainWindow::instance(QSplashScreen* screen)
MainWindow* MainWindow::instance(void)
{
return instance_mode(screen, CUSTOM_MODE_UNCHANGED);
// QGCAppication should have already called _create. Singleton is only created by call to _create
// not here.
Q_ASSERT(_instance);
return _instance;
}
/**
* Create new mainwindow. The constructor instantiates all parts of the user
* interface. It does NOT show the mainwindow. To display it, call the show()
* method.
*
* @see QMainWindow::show()
**/
MainWindow::MainWindow(QWidget *parent):
QMainWindow(parent),
/// @brief Private constructor for MainWindow. MainWindow singleton is only ever created
/// by MainWindow::_create method. Hence no other code should have access to
/// constructor.
MainWindow::MainWindow(QSplashScreen* splashScreen, enum MainWindow::CUSTOM_MODE mode) :
currentView(VIEW_FLIGHT),
currentStyle(QGC_MAINWINDOW_STYLE_DARK),
aboutToCloseFlag(false),
......@@ -124,19 +122,25 @@ MainWindow::MainWindow(QWidget *parent):
autoReconnect(false),
simulationLink(NULL),
lowPowerMode(false),
customMode(CUSTOM_MODE_NONE),
menuActionHelper(new MenuActionHelper())
customMode(mode),
menuActionHelper(new MenuActionHelper()),
_splashScreen(splashScreen)
{
Q_ASSERT(splashScreen);
Q_ASSERT(_instance == NULL);
_instance = this;
connect(this, &MainWindow::initStatusChanged, splashScreen, &QSplashScreen::showMessage);
this->setAttribute(Qt::WA_DeleteOnClose);
connect(menuActionHelper, SIGNAL(needToShowDockWidget(QString,bool)),SLOT(showDockWidget(QString,bool)));
//TODO: move protocol outside UI
connect(mavlink, SIGNAL(protocolStatusMessage(QString,QString)), this, SLOT(showCriticalMessage(QString,QString)), Qt::QueuedConnection);
connect(mavlink, &MAVLinkProtocol::protocolStatusMessage, this, &MainWindow::showCriticalMessage);
connect(mavlink, &MAVLinkProtocol::saveTempFlightDataLog, this, &MainWindow::_saveTempFlightDataLog);
loadSettings();
}
void MainWindow::init()
{
emit initStatusChanged(tr("Loading style"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
qApp->setStyle("plastique");
loadStyle(currentStyle);
......@@ -261,11 +265,15 @@ void MainWindow::init()
// Connect link
if (autoReconnect)
{
LinkManager* linkMgr = LinkManager::instance();
Q_ASSERT(linkMgr);
SerialLink* link = new SerialLink();
// Add to registry
LinkManager::instance()->add(link);
LinkManager::instance()->addProtocol(link, mavlink);
link->connect();
linkMgr->add(link);
linkMgr->addProtocol(link, mavlink);
linkMgr->connectLink(link);
}
// Set low power mode
......@@ -1122,24 +1130,16 @@ void MainWindow::showStatusMessage(const QString& status)
void MainWindow::showCriticalMessage(const QString& title, const QString& message)
{
QMessageBox msgBox(this);
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText(title);
msgBox.setInformativeText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
_hideSplashScreen();
qDebug() << "Critical" << title << message;
QMessageBox::critical(this, title, message);
}
void MainWindow::showInfoMessage(const QString& title, const QString& message)
{
QMessageBox msgBox(this);
msgBox.setIcon(QMessageBox::Information);
msgBox.setText(title);
msgBox.setInformativeText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
_hideSplashScreen();
qDebug() << "Information" << title << message;
QMessageBox::information(this, title, message);
}
/**
......@@ -1276,52 +1276,37 @@ void MainWindow::connectCommonActions()
connect(ui.actionSimulate, SIGNAL(triggered(bool)), this, SLOT(simulateLink(bool)));
}
void MainWindow::showHelp()
void MainWindow::_openUrl(const QString& url, const QString& errorMessage)
{
if(!QDesktopServices::openUrl(QUrl("http://qgroundcontrol.org/users/start")))
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText("Could not open help in browser");
msgBox.setInformativeText("To get to the online help, please open http://qgroundcontrol.org/user_guide in a browser.");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
if(!QDesktopServices::openUrl(QUrl(url))) {
QMessageBox::critical(this,
tr("Could not open information in browser"),
errorMessage);
}
}
void MainWindow::showHelp()
{
_openUrl("http://qgroundcontrol.org/users/start",
tr("To get to the online help, please open http://qgroundcontrol.org/user_guide in a browser."));
}
void MainWindow::showCredits()
{
if(!QDesktopServices::openUrl(QUrl("http://qgroundcontrol.org/credits")))
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText("Could not open credits in browser");
msgBox.setInformativeText("To get to the online help, please open http://qgroundcontrol.org/credits in a browser.");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
_openUrl("http://qgroundcontrol.org/credits",
tr("To get to the credits, please open http://qgroundcontrol.org/credits in a browser."));
}
void MainWindow::showRoadMap()
{
if(!QDesktopServices::openUrl(QUrl("http://qgroundcontrol.org/dev/roadmap")))
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText("Could not open roadmap in browser");
msgBox.setInformativeText("To get to the online help, please open http://qgroundcontrol.org/roadmap in a browser.");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
_openUrl("http://qgroundcontrol.org/dev/roadmap",
tr("To get to the online help, please open http://qgroundcontrol.org/roadmap in a browser."));
}
void MainWindow::showSettings()
{
QGCSettingsWidget* settings = new QGCSettingsWidget(joystick, this);
settings->show();
SettingsDialog settings(joystick, this);
settings.exec();
}
LinkInterface* MainWindow::addLink()
......@@ -1412,9 +1397,16 @@ void MainWindow::addLink(LinkInterface *link)
}
void MainWindow::simulateLink(bool simulate) {
if (!simulationLink)
simulationLink = new MAVLinkSimulationLink(":/demo-log.txt");
simulationLink->connectLink(simulate);
if (simulate) {
if (!simulationLink) {
simulationLink = new MAVLinkSimulationLink(":/demo-log.txt");
Q_CHECK_PTR(simulationLink);
}
LinkManager::instance()->connectLink(simulationLink);
} else {
Q_ASSERT(simulationLink);
LinkManager::instance()->disconnectLink(simulationLink);
}
}
void MainWindow::commsWidgetDestroyed(QObject *obj)
......@@ -1700,6 +1692,9 @@ void MainWindow::handleMisconfiguration(UASInterface* uas)
return;
}
}
_hideSplashScreen();
// Ask user if he wants to handle this now
QMessageBox msgBox(this);
msgBox.setIcon(QMessageBox::Information);
......@@ -1826,6 +1821,31 @@ bool MainWindow::dockWidgetTitleBarsEnabled() const
return menuActionHelper->dockWidgetTitleBarsEnabled();
}
void MainWindow::_saveTempFlightDataLog(QString tempLogfile)
{
if (qgcApp()->promptFlightDataSave()) {
_hideSplashScreen();
QString saveFilename = QFileDialog::getSaveFileName(this,
tr("Select file to save Flight Data Log"),
qgcApp()->mavlinkLogFilesLocation(),
tr("Flight Data Log (*.mavlink)"));
if (!saveFilename.isEmpty()) {
QFile::copy(tempLogfile, saveFilename);
}
}
QFile::remove(tempLogfile);
}
/// @brief Hides the spash screen if it is currently being shown
void MainWindow::_hideSplashScreen(void)
{
if (_splashScreen) {
_splashScreen->hide();
_splashScreen = NULL;
}
}
#ifdef QGC_MOUSE_ENABLED_LINUX
bool MainWindow::x11Event(XEvent *event)
{
......
......@@ -100,31 +100,18 @@ public:
CUSTOM_MODE_WIFI
};
/**
* A static function for obtaining the sole instance of the MainWindow. The screen
* argument is only important on the FIRST call to this function. The provided splash
* screen is updated with some status messages that are emitted during init(). This
* function cannot be used within the MainWindow constructor!
*/
static MainWindow* instance(QSplashScreen* screen = 0);
static MainWindow* instance_mode(QSplashScreen* screen = 0, enum MainWindow::CUSTOM_MODE mode = MainWindow::CUSTOM_MODE_NONE);
/// @brief Returns the MainWindow singleton. Will not create the MainWindow if it has not already
/// been created.
static MainWindow* instance(void);
/// @brief Creates the MainWindow singleton. Should only be called once by QGCApplication.
static MainWindow* _create(QSplashScreen* splashScreen, enum MainWindow::CUSTOM_MODE mode);
/// @brief Called to indicate that splash screen is no longer being displayed.
void splashScreenFinished(void) { _splashScreen = NULL; }
/**
* Initializes the MainWindow. Some variables are initialized and the widget is hidden.
* Initialization of the MainWindow class really occurs in init(), which loads the UI
* and does everything important. The constructor is split in two like this so that
* the instance() is available for all classes.
*/
MainWindow(QWidget *parent = NULL);
~MainWindow();
/**
* This function actually performs the non-trivial initialization of the MainWindow
* class. This is separate from the constructor because instance() won't work within
* code executed in the MainWindow constructor.
*/
void init();
enum QGC_MAINWINDOW_STYLE
{
QGC_MAINWINDOW_STYLE_DARK,
......@@ -272,7 +259,7 @@ public slots:
void configureWindowName();
void commsWidgetDestroyed(QObject *obj);
protected slots:
void showDockWidget(const QString &name, bool show);
/**
......@@ -475,8 +462,18 @@ protected:
QGCFlightGearLink* fgLink;
QTimer windowNameUpdateTimer;
CUSTOM_MODE customMode;
private slots:
/// @brief Save the specified Flight Data Log
void _saveTempFlightDataLog(QString tempLogfile);
private:
/// Constructor is private since all creation should be through MainWindow::instance.
MainWindow(QSplashScreen* splashScreen, enum MainWindow::CUSTOM_MODE mode);
void _hideSplashScreen(void);
void _openUrl(const QString& url, const QString& errorMessage);
QList<QObject*> commsWidgetList;
QMap<QString,QString> customWidgetNameToFilenameMap;
MenuActionHelper *menuActionHelper;
......@@ -489,6 +486,8 @@ private:
QString getWindowStateKey();
QString getWindowGeometryKey();
QSplashScreen* _splashScreen; ///< Splash screen, NULL is splash screen not currently being shown
friend class MenuActionHelper; //For VIEW_SECTIONS
};
......
......@@ -8,6 +8,8 @@
#include "QGCMAVLinkLogPlayer.h"
#include "QGC.h"
#include "ui_QGCMAVLinkLogPlayer.h"
#include "QGCCore.h"
#include "LinkManager.h"
QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *parent) :
QWidget(parent),
......@@ -22,9 +24,10 @@ QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *pare
binaryBaudRate(defaultBinaryBaudRate),
isPlaying(false),
currPacketCount(0),
lastLogDirectory(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)),
ui(new Ui::QGCMAVLinkLogPlayer)
{
Q_ASSERT(mavlink);
ui->setupUi(this);
ui->horizontalLayout->setAlignment(Qt::AlignTop);
......@@ -32,14 +35,18 @@ QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *pare
connect(this, SIGNAL(bytesReady(LinkInterface*,QByteArray)), mavlink, SLOT(receiveBytes(LinkInterface*,QByteArray)));
// Setup timer
connect(&loopTimer, SIGNAL(timeout()), this, SLOT(logLoop()));
connect(&loopTimer, &QTimer::timeout, this, &QGCMAVLinkLogPlayer::logLoop);
// Setup buttons
connect(ui->selectFileButton, SIGNAL(clicked()), this, SLOT(selectLogFile()));
connect(ui->playButton, SIGNAL(clicked()), this, SLOT(playPauseToggle()));
connect(ui->speedSlider, SIGNAL(valueChanged(int)), this, SLOT(setAccelerationFactorInt(int)));
connect(ui->positionSlider, SIGNAL(valueChanged(int)), this, SLOT(jumpToSliderVal(int)));
connect(ui->positionSlider, SIGNAL(sliderPressed()), this, SLOT(pause()));
connect(ui->selectFileButton, &QPushButton::clicked, this, &QGCMAVLinkLogPlayer::_selectLogFileForPlayback);
connect(ui->playButton, &QPushButton::clicked, this, &QGCMAVLinkLogPlayer::playPauseToggle);
connect(ui->speedSlider, &QSlider::valueChanged, this, &QGCMAVLinkLogPlayer::setAccelerationFactorInt);
connect(ui->positionSlider, &QSlider::valueChanged, this, &QGCMAVLinkLogPlayer::jumpToSliderVal);
connect(ui->positionSlider, &QSlider::sliderPressed, this, &QGCMAVLinkLogPlayer::pause);
// We use this to queue the signal over to mavlink. This way it will be behind any remaining
// bytesReady signals in the queue.
connect(this, &QGCMAVLinkLogPlayer::suspendLogForReplay, mavlink, &MAVLinkProtocol::suspendLogForReplay);
setAccelerationFactorInt(49);
ui->speedSlider->setValue(49);
......@@ -53,29 +60,14 @@ QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *pare
ui->logStatsLabel->setEnabled(false);
// Monitor for when the end of the log file is reached. This is done using signals because the main work is in a timer.
connect(this, SIGNAL(logFileEndReached()), &loopTimer, SLOT(stop()));
loadSettings();
connect(this, &QGCMAVLinkLogPlayer::logFileEndReached, &loopTimer, &QTimer::stop);
}
QGCMAVLinkLogPlayer::~QGCMAVLinkLogPlayer()
{
storeSettings();
delete ui;
}
void QGCMAVLinkLogPlayer::playPause(bool enabled)
{
if (enabled)
{
play();
}
else
{
pause();
}
}
void QGCMAVLinkLogPlayer::playPauseToggle()
{
if (isPlaying)
......@@ -90,55 +82,49 @@ void QGCMAVLinkLogPlayer::playPauseToggle()
void QGCMAVLinkLogPlayer::play()
{
if (logFile.isOpen())
Q_ASSERT(logFile.isOpen());
LinkManager::instance()->setConnectionsSuspended(tr("Connect not allowed during Flight Data replay."));
emit suspendLogForReplay(true);
// Disable the log file selector button
ui->selectFileButton->setEnabled(false);
// Make sure we aren't at the end of the file, if we are, reset to the beginning and play from there.
if (logFile.atEnd())
{
// Disable the log file selector button
ui->selectFileButton->setEnabled(false);
// Make sure we aren't at the end of the file, if we are, reset to the beginning and play from there.
if (logFile.atEnd())
{
reset();
}
reset();
}
// Always correct the current start time such that the next message will play immediately at playback.
// We do this by subtracting the current file playback offset from now()
playbackStartTime = (quint64)QDateTime::currentMSecsSinceEpoch() - (logCurrentTime - logStartTime) / 1000;
// Always correct the current start time such that the next message will play immediately at playback.
// We do this by subtracting the current file playback offset from now()
playbackStartTime = (quint64)QDateTime::currentMSecsSinceEpoch() - (logCurrentTime - logStartTime) / 1000;
// Start timer
if (mavlinkLogFormat)
{
loopTimer.start(1);
}
else
{
// Read len bytes at a time
int len = 100;
// Calculate the number of times to read 100 bytes per second
// to guarantee the baud rate, then divide 1000 by the number of read
// operations to obtain the interval in milliseconds
int interval = 1000 / ((binaryBaudRate / 10) / len);
loopTimer.start(interval / accelerationFactor);
}
isPlaying = true;
ui->playButton->setChecked(true);
ui->playButton->setIcon(QIcon(":files/images/actions/media-playback-pause.svg"));
// Start timer
if (mavlinkLogFormat)
{
loopTimer.start(1);
}
else
{
ui->playButton->setChecked(false);
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Information);
msgBox.setText(tr("No logfile selected"));
msgBox.setInformativeText(tr("Please select first a MAVLink log file before playing it."));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
// Read len bytes at a time
int len = 100;
// Calculate the number of times to read 100 bytes per second
// to guarantee the baud rate, then divide 1000 by the number of read
// operations to obtain the interval in milliseconds
int interval = 1000 / ((binaryBaudRate / 10) / len);
loopTimer.start(interval / accelerationFactor);
}
isPlaying = true;
ui->playButton->setChecked(true);
ui->playButton->setIcon(QIcon(":files/images/actions/media-playback-pause.svg"));
}
void QGCMAVLinkLogPlayer::pause()
{
LinkManager::instance()->setConnectionsAllowed();
emit suspendLogForReplay(false);
loopTimer.stop();
isPlaying = false;
ui->playButton->setIcon(QIcon(":files/images/actions/media-playback-start.svg"));
......@@ -258,51 +244,33 @@ void QGCMAVLinkLogPlayer::updatePositionSliderUi(float percent)
ui->positionSlider->blockSignals(false);
}
void QGCMAVLinkLogPlayer::loadSettings()
/// @brief Displays a file dialog to allow the user to select a log file to play back.
void QGCMAVLinkLogPlayer::_selectLogFileForPlayback(void)
{
QSettings settings;
settings.beginGroup("QGC_MAVLINKLOGPLAYER");
lastLogDirectory = settings.value("LAST_LOG_DIRECTORY", lastLogDirectory).toString();
settings.endGroup();
}
void QGCMAVLinkLogPlayer::storeSettings()
{
QSettings settings;
settings.beginGroup("QGC_MAVLINKLOGPLAYER");
settings.setValue("LAST_LOG_DIRECTORY", lastLogDirectory);
settings.endGroup();
settings.sync();
}
/**
* @brief Select a log file
* @param startDirectory Directory where the file dialog will be opened
* @return filename of the logFile
*/
bool QGCMAVLinkLogPlayer::selectLogFile()
{
// Prompt the user for a new file using the last directory they searched.
return selectLogFile(lastLogDirectory);
}
/**
* @brief Select a log file
* @param startDirectory Directory where the file dialog will be opened
* @return filename of the logFile
*/
bool QGCMAVLinkLogPlayer::selectLogFile(const QString startDirectory)
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Specify MAVLink log file name to replay"), startDirectory, tr("MAVLink or Binary Logfile (*.mavlink *.bin *.log)"));
if (fileName == "")
{
return false;
// Disallow replay when any links are connected
bool foundConnection = false;
LinkManager* linkMgr = LinkManager::instance();
QList<LinkInterface*> links = linkMgr->getLinks();
foreach(LinkInterface* link, links) {
if (link->isConnected()) {
foundConnection = true;
break;
}
}
else
{
lastLogDirectory = fileName;
return loadLogFile(fileName);
if (foundConnection) {
MainWindow::instance()->showInfoMessage(tr("Log Replay"), tr("You must close all connections prior to replaying a log."));
return;
}
QString logFile = QFileDialog::getOpenFileName(this,
tr("Specify MAVLink log file name to replay"),
qgcApp()->mavlinkLogFilesLocation(),
tr("MAVLink or Binary Logfile (*.mavlink *.bin *.log)"));
if (!logFile.isEmpty()) {
loadLogFile(logFile);
}
}
......@@ -341,157 +309,125 @@ void QGCMAVLinkLogPlayer::setAccelerationFactorInt(int factor)
bool QGCMAVLinkLogPlayer::loadLogFile(const QString& file)
{
// Enable controls
ui->playButton->setEnabled(true);
ui->speedSlider->setEnabled(true);
ui->positionSlider->setEnabled(true);
ui->speedLabel->setEnabled(true);
ui->logFileNameLabel->setEnabled(true);
ui->logStatsLabel->setEnabled(true);
// Disable logging while replaying a log file.
if (mavlink->loggingEnabled())
{
mavlink->enableLogging(false);
MainWindow::instance()->showInfoMessage(tr("MAVLink Logging Stopped during Replay"), tr("MAVLink logging has been stopped during the log replay. To re-enable logging, use the link properties in the communication menu."));
}
// Make sure to stop the logging process and reset everything.
reset();
// And that the old file is closed nicely.
if (logFile.isOpen())
{
logFile.close();
}
logFile.close();
// Now load the new file.
logFile.setFileName(file);
if (!logFile.open(QFile::ReadOnly))
{
MainWindow::instance()->showCriticalMessage(tr("The selected logfile is unreadable"), tr("Please make sure that the file %1 is readable or select a different file").arg(file));
logFile.setFileName("");
if (!logFile.open(QFile::ReadOnly)) {
MainWindow::instance()->showCriticalMessage(tr("The selected file is unreadable"), tr("Please make sure that the file %1 is readable or select a different file").arg(file));
_playbackError();
return false;
}
else
{
QFileInfo logFileInfo(file);
logFile.reset();
ui->logFileNameLabel->setText(tr("Logfile: %1").arg(logFileInfo.fileName()));
// If there's an existing MAVLinkSimulationLink() being used for an old file,
// we replace it.
if (logLink)
{
logLink->disconnect();
LinkManager::instance()->removeLink(logLink);
delete logLink;
}
logLink = new MAVLinkSimulationLink("");
QFileInfo logFileInfo(file);
ui->logFileNameLabel->setText(tr("File: %1").arg(logFileInfo.fileName()));
// If there's an existing MAVLinkSimulationLink() being used for an old file,
// we replace it.
if (logLink)
{
LinkManager::instance()->disconnectLink(logLink);
LinkManager::instance()->removeLink(logLink);
logLink->deleteLater();
}
logLink = new MAVLinkSimulationLink("");
// Select if binary or MAVLink log format is used
mavlinkLogFormat = file.endsWith(".mavlink");
if (mavlinkLogFormat)
{
// Get the first timestamp from the logfile
// This should be a big-endian uint64.
QByteArray timestamp = logFile.read(timeLen);
quint64 starttime = parseTimestamp(timestamp);
// Now find the last timestamp by scanning for the last MAVLink packet and
// find the timestamp before it. To do this we start searchin a little before
// the end of the file, specifically the maximum MAVLink packet size + the
// timestamp size. This guarantees that we will hit a MAVLink packet before
// the end of the file. Unfortunately, it basically guarantees that we will
// hit more than one. This is why we have to search for a bit.
qint64 fileLoc = logFile.size() - MAVLINK_MAX_PACKET_LEN - timeLen;
logFile.seek(fileLoc);
quint64 endtime = starttime; // Set a sane default for the endtime
mavlink_message_t msg;
quint64 newTimestamp;
while ((newTimestamp = findNextMavlinkMessage(&msg)) > endtime) {
endtime = newTimestamp;
}
// Select if binary or MAVLink log format is used
mavlinkLogFormat = file.endsWith(".mavlink");
if (endtime == starttime) {
MainWindow::instance()->showCriticalMessage(tr("The selected logfile cannot be processed"), tr("No valid timestamps were found at the end of the logfile.").arg(file));
logFile.setFileName("");
ui->logFileNameLabel->setText(tr("No logfile selected"));
return false;
}
if (mavlinkLogFormat)
{
// Get the first timestamp from the logfile
// This should be a big-endian uint64.
QByteArray timestamp = logFile.read(timeLen);
quint64 starttime = parseTimestamp(timestamp);
// Now find the last timestamp by scanning for the last MAVLink packet and
// find the timestamp before it. To do this we start searchin a little before
// the end of the file, specifically the maximum MAVLink packet size + the
// timestamp size. This guarantees that we will hit a MAVLink packet before
// the end of the file. Unfortunately, it basically guarantees that we will
// hit more than one. This is why we have to search for a bit.
qint64 fileLoc = logFile.size() - MAVLINK_MAX_PACKET_LEN - timeLen;
logFile.seek(fileLoc);
quint64 endtime = starttime; // Set a sane default for the endtime
mavlink_message_t msg;
quint64 newTimestamp;
while ((newTimestamp = findNextMavlinkMessage(&msg)) > endtime) {
endtime = newTimestamp;
}
// Remember the start and end time so we can move around this logfile with the slider.
logEndTime = endtime;
logStartTime = starttime;
logCurrentTime = logStartTime;
if (endtime == starttime) {
MainWindow::instance()->showCriticalMessage(tr("The selected file is corrupt"), tr("No valid timestamps were found at the end of the file.").arg(file));
_playbackError();
return false;
}
// Reset our log file so when we go to read it for the first time, we start at the beginning.
logFile.reset();
// Remember the start and end time so we can move around this logfile with the slider.
logEndTime = endtime;
logStartTime = starttime;
logCurrentTime = logStartTime;
// Calculate the runtime in hours:minutes:seconds
// WARNING: Order matters in this computation
quint32 seconds = (endtime - starttime)/1000000;
quint32 minutes = seconds / 60;
quint32 hours = minutes / 60;
seconds -= 60*minutes;
minutes -= 60*hours;
// Reset our log file so when we go to read it for the first time, we start at the beginning.
logFile.reset();
// And show the user the details we found about this file.
QString timelabel = tr("%1h:%2m:%3s").arg(hours, 2).arg(minutes, 2).arg(seconds, 2);
currPacketCount = logFileInfo.size()/(32 + MAVLINK_NUM_NON_PAYLOAD_BYTES + sizeof(quint64)); // Count packets by assuming an average payload size of 32 bytes
ui->logStatsLabel->setText(tr("%2 MB, ~%3 packets, %4").arg(logFileInfo.size()/1000000.0f, 0, 'f', 2).arg(currPacketCount).arg(timelabel));
}
else
// Calculate the runtime in hours:minutes:seconds
// WARNING: Order matters in this computation
quint32 seconds = (endtime - starttime)/1000000;
quint32 minutes = seconds / 60;
quint32 hours = minutes / 60;
seconds -= 60*minutes;
minutes -= 60*hours;
// And show the user the details we found about this file.
QString timelabel = tr("%1h:%2m:%3s").arg(hours, 2).arg(minutes, 2).arg(seconds, 2);
currPacketCount = logFileInfo.size()/(32 + MAVLINK_NUM_NON_PAYLOAD_BYTES + sizeof(quint64)); // Count packets by assuming an average payload size of 32 bytes
ui->logStatsLabel->setText(tr("%2 MB, ~%3 packets, %4").arg(logFileInfo.size()/1000000.0f, 0, 'f', 2).arg(currPacketCount).arg(timelabel));
}
else
{
// Load in binary mode. In this mode, files should be have a filename postfix
// of the baud rate they were recorded at, like `test_run_115200.bin`. Then on
// playback, the datarate is equal to set to this value.
// Set baud rate if any present. Otherwise we default to 57600.
QStringList parts = logFileInfo.baseName().split("_");
binaryBaudRate = defaultBinaryBaudRate;
if (parts.count() > 1)
{
// Load in binary mode. In this mode, files should be have a filename postfix
// of the baud rate they were recorded at, like `test_run_115200.bin`. Then on
// playback, the datarate is equal to set to this value.
// Set baud rate if any present. Otherwise we default to 57600.
QStringList parts = logFileInfo.baseName().split("_");
binaryBaudRate = defaultBinaryBaudRate;
if (parts.count() > 1)
bool ok;
int rate = parts.last().toInt(&ok);
// 9600 baud to 100 MBit
if (ok && (rate > 9600 && rate < 100000000))
{
bool ok;
int rate = parts.last().toInt(&ok);
// 9600 baud to 100 MBit
if (ok && (rate > 9600 && rate < 100000000))
{
// Accept this as valid baudrate
binaryBaudRate = rate;
}
// Accept this as valid baudrate
binaryBaudRate = rate;
}
int seconds = logFileInfo.size() / (binaryBaudRate / 10);
int minutes = seconds / 60;
int hours = minutes / 60;
seconds -= 60*minutes;
minutes -= 60*hours;
QString timelabel = tr("%1h:%2m:%3s").arg(hours, 2).arg(minutes, 2).arg(seconds, 2);
ui->logStatsLabel->setText(tr("%2 MB, %4 at %5 KB/s").arg(logFileInfo.size()/1000000.0f, 0, 'f', 2).arg(timelabel).arg(binaryBaudRate/10.0f/1024.0f, 0, 'f', 2));
}
// Check if a serial link is connected
bool linkWarning = false;
foreach (LinkInterface* link, LinkManager::instance()->getLinks())
{
SerialLink* s = dynamic_cast<SerialLink*>(link);
if (s && s->isConnected())
linkWarning = true;
}
int seconds = logFileInfo.size() / (binaryBaudRate / 10);
int minutes = seconds / 60;
int hours = minutes / 60;
seconds -= 60*minutes;
minutes -= 60*hours;
if (linkWarning)
MainWindow::instance()->showInfoMessage(tr("Active MAVLink links found"), tr("Currently other links are connected. It is recommended to disconnect any active link before replaying a log."));
QString timelabel = tr("%1h:%2m:%3s").arg(hours, 2).arg(minutes, 2).arg(seconds, 2);
ui->logStatsLabel->setText(tr("%2 MB, %4 at %5 KB/s").arg(logFileInfo.size()/1000000.0f, 0, 'f', 2).arg(timelabel).arg(binaryBaudRate/10.0f/1024.0f, 0, 'f', 2));
}
play();
// Enable controls
ui->playButton->setEnabled(true);
ui->speedSlider->setEnabled(true);
ui->positionSlider->setEnabled(true);
ui->speedLabel->setEnabled(true);
ui->logFileNameLabel->setEnabled(true);
ui->logStatsLabel->setEnabled(true);
play();
return true;
}
return true;
}
quint64 QGCMAVLinkLogPlayer::parseTimestamp(const QByteArray &data)
......@@ -593,17 +529,8 @@ void QGCMAVLinkLogPlayer::logLoop()
emit bytesReady(logLink, message);
// If we've reached the end of the of the file, make sure we handle that well
if (logFile.atEnd())
{
// For some reason calling pause() here doesn't work, so we update the UI manually here.
isPlaying = false;
ui->playButton->setIcon(QIcon(":files/images/actions/media-playback-start.svg"));
ui->playButton->setChecked(false);
ui->selectFileButton->setEnabled(true);
// Note that we explicitly set the slider to 100%, as it may not hit that by itself depending on log file size.
updatePositionSliderUi(100.0f);
emit logFileEndReached();
if (logFile.atEnd()) {
_finishPlayback();
return;
}
......@@ -633,12 +560,7 @@ void QGCMAVLinkLogPlayer::logLoop()
// Check if reached end of file before reading next timestamp
if (chunk.length() < len || logFile.atEnd())
{
// Reached end of file
reset();
QString status = tr("Reached end of binary log file.");
ui->logStatsLabel->setText(status);
MainWindow::instance()->showStatusMessage(status);
_finishPlayback();
return;
}
}
......@@ -707,3 +629,40 @@ void QGCMAVLinkLogPlayer::paintEvent(QPaintEvent *)
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
/// @brief Called when playback is complete
void QGCMAVLinkLogPlayer::_finishPlayback(void)
{
pause();
QString status = tr("Flight Data replay complete");
ui->logStatsLabel->setText(status);
MainWindow::instance()->showStatusMessage(status);
// Note that we explicitly set the slider to 100%, as it may not hit that by itself depending on log file size.
updatePositionSliderUi(100.0f);
emit logFileEndReached();
emit suspendLogForReplay(false);
LinkManager::instance()->setConnectionsAllowed();
}
/// @brief Called when an error occurs during playback to reset playback system state.
void QGCMAVLinkLogPlayer::_playbackError(void)
{
pause();
logFile.close();
logFile.setFileName("");
ui->logFileNameLabel->setText(tr("No Flight Data selected"));
// Disable playback controls
ui->playButton->setEnabled(false);
ui->speedSlider->setEnabled(false);
ui->positionSlider->setEnabled(false);
ui->speedLabel->setEnabled(false);
ui->logFileNameLabel->setEnabled(false);
ui->logStatsLabel->setEnabled(false);
}
......@@ -37,29 +37,15 @@ public:
return logFile.isOpen();
}
/**
* @brief Set the last log file name
* @param filename
*/
void setLastLogFile(const QString& filename) {
lastLogDirectory = filename;
}
public slots:
/** @brief Toggle between play and pause */
void playPauseToggle();
/** @brief Play / pause the log */
void playPause(bool play);
/** @brief Replay the logfile */
void play();
/** @brief Pause the log player. */
void pause();
/** @brief Reset the internal log player state, including the UI */
void reset();
/** @brief Select logfile */
bool selectLogFile(const QString startDirectory);
/** @brief Select logfile */
bool selectLogFile();
/** @brief Load log file */
bool loadLogFile(const QString& file);
/** @brief Jump to a position in the logfile */
......@@ -73,6 +59,9 @@ signals:
/** @brief Send ready bytes */
void bytesReady(LinkInterface* link, const QByteArray& bytes);
void logFileEndReached();
/// @brief Connected to the MAVLinkProtocol::suspendLogForReplay
void suspendLogForReplay(bool suspend);
protected:
quint64 playbackStartTime; ///< The time when the logfile was first played back. This is used to pace out replaying the messages to fix long-term drift/skew. 0 indicates that the player hasn't initiated playback of this log file. In units of milliseconds since epoch UTC.
......@@ -92,11 +81,10 @@ protected:
unsigned int currPacketCount;
static const int packetLen = MAVLINK_MAX_PACKET_LEN;
static const int timeLen = sizeof(quint64);
QString lastLogDirectory;
void changeEvent(QEvent *e);
void loadSettings();
void storeSettings();
private slots:
void _selectLogFileForPlayback(void);
private:
Ui::QGCMAVLinkLogPlayer *ui;
......@@ -130,6 +118,9 @@ private:
* @return True if the new file position was successfully jumped to, false otherwise
*/
bool jumpToPlaybackLocation(float percentage);
void _finishPlayback(void);
void _playbackError(void);
};
#endif // QGCMAVLINKLOGPLAYER_H
......@@ -36,13 +36,13 @@
<item>
<widget class="QToolButton" name="playButton">
<property name="toolTip">
<string>Start to replay the logfile</string>
<string>Start to replay Flight Data</string>
</property>
<property name="statusTip">
<string>Start to replay the logfile</string>
<string>Start to replay Flight Data</string>
</property>
<property name="whatsThis">
<string>Start to replay the logfile</string>
<string>Start to replay Flight Data</string>
</property>
<property name="text">
<string>...</string>
......@@ -129,23 +129,23 @@
<item>
<widget class="QLabel" name="logFileNameLabel">
<property name="text">
<string>No logfile selected..</string>
<string>No Flight Data selected..</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="selectFileButton">
<property name="toolTip">
<string>Select the logfile to replay</string>
<string>Select the Flight Data to replay</string>
</property>
<property name="statusTip">
<string>Select the logfile to replay</string>
<string>Select the Flight Data to replay</string>
</property>
<property name="whatsThis">
<string>Select the logfile to replay</string>
<string>Select the Flight Data to replay</string>
</property>
<property name="text">
<string>Replay Logfile</string>
<string>Replay Flight Data</string>
</property>
</widget>
</item>
......
......@@ -27,21 +27,15 @@ This file is part of the QGROUNDCONTROL project
#include "QGCStatusBar.h"
#include "UASManager.h"
#include "MainWindow.h"
#include "QGCCore.h"
QGCStatusBar::QGCStatusBar(QWidget *parent) :
QStatusBar(parent),
toggleLoggingButton(NULL),
player(NULL),
changed(true),
lastLogDirectory(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation))
{
setObjectName("QGC_STATUSBAR");
toggleLoggingButton = new QPushButton(tr("Log to file"), this);
toggleLoggingButton->setCheckable(true);
addPermanentWidget(toggleLoggingButton);
loadSettings();
}
......@@ -58,58 +52,6 @@ void QGCStatusBar::setLogPlayer(QGCMAVLinkLogPlayer* player)
{
this->player = player;
addPermanentWidget(player);
connect(toggleLoggingButton, SIGNAL(clicked(bool)), this, SLOT(logging(bool)));
}
void QGCStatusBar::logging(bool checked)
{
// Stop logging in any case
MainWindow::instance()->getMAVLink()->enableLogging(false);
if (!checked && player)
{
player->setLastLogFile(lastLogDirectory);
}
// If the user is enabling logging
if (checked)
{
// Prompt the user for a filename/location to save to
QString fileName = QFileDialog::getSaveFileName(this, tr("Specify MAVLink log file to save to"), lastLogDirectory, tr("MAVLink Logfile (*.mavlink *.log *.bin);;"));
// Check that they didn't cancel out
if (fileName.isNull())
{
toggleLoggingButton->setChecked(false);
return;
}
// Make sure the file's named properly
if (!fileName.endsWith(".mavlink"))
{
fileName.append(".mavlink");
}
// Check that we can save the logfile
QFileInfo file(fileName);
if ((file.exists() && !file.isWritable()))
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText(tr("The selected logfile is not writable"));
msgBox.setInformativeText(tr("Please make sure that the file %1 is writable or select a different file").arg(fileName));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
// Otherwise we're off and logging
else
{
MainWindow::instance()->getMAVLink()->setLogfileName(fileName);
MainWindow::instance()->getMAVLink()->enableLogging(true);
lastLogDirectory = file.absoluteDir().absolutePath(); //save last log directory
}
}
}
void QGCStatusBar::loadSettings()
......@@ -132,5 +74,4 @@ void QGCStatusBar::storeSettings()
QGCStatusBar::~QGCStatusBar()
{
storeSettings();
if (toggleLoggingButton) toggleLoggingButton->deleteLater();
}
......@@ -43,8 +43,6 @@ public:
~QGCStatusBar();
public slots:
/** @brief Start / stop logging */
void logging(bool checked);
/** @brief Set log playing component */
void setLogPlayer(QGCMAVLinkLogPlayer* player);
virtual void paintEvent(QPaintEvent * event);
......@@ -53,7 +51,6 @@ protected:
void storeSettings();
void loadSettings();
QPushButton* toggleLoggingButton;
QGCMAVLinkLogPlayer* player;
bool changed;
QString lastLogDirectory;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment