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 @@ ...@@ -30,9 +30,13 @@
#include "QGCMAVLink.h" #include "QGCMAVLink.h"
#include "QGCMAVLinkUASFactory.h" #include "QGCMAVLinkUASFactory.h"
#include "QGC.h" #include "QGC.h"
#include "QGCCore.h"
Q_DECLARE_METATYPE(mavlink_message_t) 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 default constructor will create a new MAVLink object sending heartbeats at
* the MAVLINK_HEARTBEAT_DEFAULT_RATE to all connected links. * the MAVLINK_HEARTBEAT_DEFAULT_RATE to all connected links.
...@@ -43,8 +47,6 @@ MAVLinkProtocol::MAVLinkProtocol() : ...@@ -43,8 +47,6 @@ MAVLinkProtocol::MAVLinkProtocol() :
m_heartbeatsEnabled(true), m_heartbeatsEnabled(true),
m_multiplexingEnabled(false), m_multiplexingEnabled(false),
m_authEnabled(false), m_authEnabled(false),
m_loggingEnabled(false),
m_logfile(NULL),
m_enable_version_check(true), m_enable_version_check(true),
m_paramRetransmissionTimeout(350), m_paramRetransmissionTimeout(350),
m_paramRewriteTimeout(500), m_paramRewriteTimeout(500),
...@@ -53,9 +55,17 @@ MAVLinkProtocol::MAVLinkProtocol() : ...@@ -53,9 +55,17 @@ MAVLinkProtocol::MAVLinkProtocol() :
m_actionRetransmissionTimeout(100), m_actionRetransmissionTimeout(100),
versionMismatchIgnore(false), versionMismatchIgnore(false),
systemId(QGC::defaultSystemId), systemId(QGC::defaultSystemId),
_should_exit(false) _should_exit(false),
_logSuspendError(false),
_logSuspendReplay(false),
_protocolStatusMessageConnected(false),
_saveTempFlightDataLogConnected(false)
{ {
qRegisterMetaType<mavlink_message_t>("mavlink_message_t"); 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"; m_authKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
loadSettings(); loadSettings();
...@@ -88,18 +98,6 @@ void MAVLinkProtocol::loadSettings() ...@@ -88,18 +98,6 @@ void MAVLinkProtocol::loadSettings()
enableVersionCheck(settings.value("VERSION_CHECK_ENABLED", m_enable_version_check).toBool()); enableVersionCheck(settings.value("VERSION_CHECK_ENABLED", m_enable_version_check).toBool());
enableMultiplexing(settings.value("MULTIPLEXING_ENABLED", m_multiplexingEnabled).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 // Only set system id if it was valid
int temp = settings.value("GCS_SYSTEM_ID", systemId).toInt(); int temp = settings.value("GCS_SYSTEM_ID", systemId).toInt();
if (temp > 0 && temp < 256) if (temp > 0 && temp < 256)
...@@ -127,17 +125,11 @@ void MAVLinkProtocol::storeSettings() ...@@ -127,17 +125,11 @@ void MAVLinkProtocol::storeSettings()
QSettings settings; QSettings settings;
settings.beginGroup("QGC_MAVLINK_PROTOCOL"); settings.beginGroup("QGC_MAVLINK_PROTOCOL");
settings.setValue("HEARTBEATS_ENABLED", m_heartbeatsEnabled); settings.setValue("HEARTBEATS_ENABLED", m_heartbeatsEnabled);
settings.setValue("LOGGING_ENABLED", m_loggingEnabled);
settings.setValue("VERSION_CHECK_ENABLED", m_enable_version_check); settings.setValue("VERSION_CHECK_ENABLED", m_enable_version_check);
settings.setValue("MULTIPLEXING_ENABLED", m_multiplexingEnabled); settings.setValue("MULTIPLEXING_ENABLED", m_multiplexingEnabled);
settings.setValue("GCS_SYSTEM_ID", systemId); settings.setValue("GCS_SYSTEM_ID", systemId);
settings.setValue("GCS_AUTH_KEY", m_authKey); settings.setValue("GCS_AUTH_KEY", m_authKey);
settings.setValue("GCS_AUTH_ENABLED", m_authEnabled); 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 // Parameter interface settings
settings.setValue("PARAMETER_RETRANSMISSION_TIMEOUT", m_paramRetransmissionTimeout); settings.setValue("PARAMETER_RETRANSMISSION_TIMEOUT", m_paramRetransmissionTimeout);
settings.setValue("PARAMETER_REWRITE_TIMEOUT", m_paramRewriteTimeout); settings.setValue("PARAMETER_REWRITE_TIMEOUT", m_paramRewriteTimeout);
...@@ -150,16 +142,8 @@ void MAVLinkProtocol::storeSettings() ...@@ -150,16 +142,8 @@ void MAVLinkProtocol::storeSettings()
MAVLinkProtocol::~MAVLinkProtocol() MAVLinkProtocol::~MAVLinkProtocol()
{ {
storeSettings(); storeSettings();
if (m_logfile)
{ _closeLogFile();
if (m_logfile->isOpen())
{
m_logfile->flush();
m_logfile->close();
}
delete m_logfile;
m_logfile = NULL;
}
// Tell the thread to exit // Tell the thread to exit
_should_exit = true; _should_exit = true;
...@@ -192,18 +176,6 @@ void MAVLinkProtocol::run() ...@@ -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) void MAVLinkProtocol::resetMetadataForLink(const LinkInterface *link)
{ {
int linkId = link->getId(); int linkId = link->getId();
...@@ -217,17 +189,47 @@ void MAVLinkProtocol::resetMetadataForLink(const LinkInterface *link) ...@@ -217,17 +189,47 @@ void MAVLinkProtocol::resetMetadataForLink(const LinkInterface *link)
void MAVLinkProtocol::linkStatusChanged(bool connected) void MAVLinkProtocol::linkStatusChanged(bool connected)
{ {
LinkInterface* link = qobject_cast<LinkInterface*>(QObject::sender()); 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 (link) {
if (connected) { 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) ...@@ -314,8 +316,10 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
} }
// Log data // 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)]; uint8_t buf[MAVLINK_MAX_PACKET_LEN+sizeof(quint64)];
// Write the uint64 time in microseconds in big endian format before the message. // Write the uint64 time in microseconds in big endian format before the message.
...@@ -332,11 +336,12 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b) ...@@ -332,11 +336,12 @@ void MAVLinkProtocol::receiveBytes(LinkInterface* link, QByteArray b)
// Now write this timestamp/message pair to the log. // Now write this timestamp/message pair to the log.
QByteArray b((const char*)buf, len); 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. // 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())); emit protocolStatusMessage(tr("MAVLink Logging failed"), tr("Could not write to file %1, logging disabled.").arg(_tempLogFile.fileName()));
enableLogging(false); _stopLogging();
_logSuspendError = true;
} }
} }
...@@ -633,62 +638,6 @@ void MAVLinkProtocol::setActionRetransmissionTimeout(int ms) ...@@ -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) void MAVLinkProtocol::enableVersionCheck(bool enabled)
{ {
m_enable_version_check = enabled; m_enable_version_check = enabled;
...@@ -711,3 +660,103 @@ int MAVLinkProtocol::getHeartbeatRate() ...@@ -711,3 +660,103 @@ int MAVLinkProtocol::getHeartbeatRate()
{ {
return heartbeatRate; 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 ...@@ -37,6 +37,8 @@ This file is part of the QGROUNDCONTROL project
#include <QFile> #include <QFile>
#include <QMap> #include <QMap>
#include <QByteArray> #include <QByteArray>
#include <QTemporaryFile>
#include "ProtocolInterface.h" #include "ProtocolInterface.h"
#include "LinkInterface.h" #include "LinkInterface.h"
#include "QGCMAVLink.h" #include "QGCMAVLink.h"
...@@ -69,10 +71,7 @@ public: ...@@ -69,10 +71,7 @@ public:
bool heartbeatsEnabled() const { bool heartbeatsEnabled() const {
return m_heartbeatsEnabled; return m_heartbeatsEnabled;
} }
/** @brief Get logging state */
bool loggingEnabled() const {
return m_loggingEnabled;
}
/** @brief Get protocol version check state */ /** @brief Get protocol version check state */
bool versionCheckEnabled() const { bool versionCheckEnabled() const {
return m_enable_version_check; return m_enable_version_check;
...@@ -93,8 +92,6 @@ public: ...@@ -93,8 +92,6 @@ public:
QString getAuthKey() { QString getAuthKey() {
return m_authKey; return m_authKey;
} }
/** @brief Get the name of the packet log file */
QString getLogfileName();
/** @brief Get state of parameter retransmission */ /** @brief Get state of parameter retransmission */
bool paramGuardEnabled() { bool paramGuardEnabled() {
return m_paramGuardEnabled; return m_paramGuardEnabled;
...@@ -140,7 +137,7 @@ public: ...@@ -140,7 +137,7 @@ public:
* Reset the counters for all metadata for this link. * Reset the counters for all metadata for this link.
*/ */
virtual void resetMetadataForLink(const LinkInterface *link); virtual void resetMetadataForLink(const LinkInterface *link);
void run(); void run();
public slots: public slots:
...@@ -161,9 +158,6 @@ public slots: ...@@ -161,9 +158,6 @@ public slots:
/** @brief Enable / disable the heartbeat emission */ /** @brief Enable / disable the heartbeat emission */
void enableHeartbeats(bool enabled); void enableHeartbeats(bool enabled);
/** @brief Enable/disable binary packet logging */
void enableLogging(bool enabled);
/** @brief Enabled/disable packet multiplexing */ /** @brief Enabled/disable packet multiplexing */
void enableMultiplexing(bool enabled); void enableMultiplexing(bool enabled);
...@@ -182,9 +176,6 @@ public slots: ...@@ -182,9 +176,6 @@ public slots:
/** @brief Set parameter read timeout */ /** @brief Set parameter read timeout */
void setActionRetransmissionTimeout(int ms); void setActionRetransmissionTimeout(int ms);
/** @brief Set log file name */
void setLogfileName(const QString& filename);
/** @brief Enable / disable version check */ /** @brief Enable / disable version check */
void enableVersionCheck(bool enabled); void enableVersionCheck(bool enabled);
...@@ -203,16 +194,22 @@ public slots: ...@@ -203,16 +194,22 @@ public slots:
void loadSettings(); void loadSettings();
/** @brief Store protocol settings */ /** @brief Store protocol settings */
void storeSettings(); 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 QTimer *heartbeatTimer; ///< Timer to emit heartbeats
int heartbeatRate; ///< Heartbeat rate, controls the timer interval int heartbeatRate; ///< Heartbeat rate, controls the timer interval
bool m_heartbeatsEnabled; ///< Enabled/disable heartbeat emission bool m_heartbeatsEnabled; ///< Enabled/disable heartbeat emission
bool m_multiplexingEnabled; ///< Enable/disable packet multiplexing bool m_multiplexingEnabled; ///< Enable/disable packet multiplexing
bool m_authEnabled; ///< Enable authentication token broadcast bool m_authEnabled; ///< Enable authentication token broadcast
QString m_authKey; ///< Authentication key 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 bool m_enable_version_check; ///< Enable checking of version match of MAV and QGC
int m_paramRetransmissionTimeout; ///< Timeout for parameter retransmission int m_paramRetransmissionTimeout; ///< Timeout for parameter retransmission
int m_paramRewriteTimeout; ///< Timeout for sending re-write request int m_paramRewriteTimeout; ///< Timeout for sending re-write request
...@@ -235,8 +232,6 @@ signals: ...@@ -235,8 +232,6 @@ signals:
void messageReceived(LinkInterface* link, mavlink_message_t message); void messageReceived(LinkInterface* link, mavlink_message_t message);
/** @brief Emitted if heartbeat emission mode is changed */ /** @brief Emitted if heartbeat emission mode is changed */
void heartbeatChanged(bool heartbeats); void heartbeatChanged(bool heartbeats);
/** @brief Emitted if logging is started / stopped */
void loggingChanged(bool enabled);
/** @brief Emitted if multiplexing is started / stopped */ /** @brief Emitted if multiplexing is started / stopped */
void multiplexingChanged(bool enabled); void multiplexingChanged(bool enabled);
/** @brief Emitted if authentication support is enabled / disabled */ /** @brief Emitted if authentication support is enabled / disabled */
...@@ -272,6 +267,27 @@ signals: ...@@ -272,6 +267,27 @@ signals:
*/ */
void radioStatusChanged(LinkInterface* link, unsigned rxerrors, unsigned fixed, unsigned rssi, unsigned remrssi, void radioStatusChanged(LinkInterface* link, unsigned rxerrors, unsigned fixed, unsigned rssi, unsigned remrssi,
unsigned txbuf, unsigned noise, unsigned remnoise); 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_ #endif // MAVLINKPROTOCOL_H_
...@@ -55,7 +55,6 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget ...@@ -55,7 +55,6 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget
// Initialize state // Initialize state
m_ui->heartbeatCheckBox->setChecked(protocol->heartbeatsEnabled()); m_ui->heartbeatCheckBox->setChecked(protocol->heartbeatsEnabled());
m_ui->loggingCheckBox->setChecked(protocol->loggingEnabled());
m_ui->versionCheckBox->setChecked(protocol->versionCheckEnabled()); m_ui->versionCheckBox->setChecked(protocol->versionCheckEnabled());
m_ui->multiplexingCheckBox->setChecked(protocol->multiplexingEnabled()); m_ui->multiplexingCheckBox->setChecked(protocol->multiplexingEnabled());
m_ui->systemIdSpinBox->setValue(protocol->getSystemId()); m_ui->systemIdSpinBox->setValue(protocol->getSystemId());
...@@ -71,14 +70,9 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget ...@@ -71,14 +70,9 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget
// Heartbeat // Heartbeat
connect(protocol, SIGNAL(heartbeatChanged(bool)), m_ui->heartbeatCheckBox, SLOT(setChecked(bool))); connect(protocol, SIGNAL(heartbeatChanged(bool)), m_ui->heartbeatCheckBox, SLOT(setChecked(bool)));
connect(m_ui->heartbeatCheckBox, SIGNAL(toggled(bool)), protocol, SLOT(enableHeartbeats(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 // Version check
connect(protocol, SIGNAL(versionCheckChanged(bool)), m_ui->versionCheckBox, SLOT(setChecked(bool))); connect(protocol, SIGNAL(versionCheckChanged(bool)), m_ui->versionCheckBox, SLOT(setChecked(bool)));
connect(m_ui->versionCheckBox, SIGNAL(toggled(bool)), protocol, SLOT(enableVersionCheck(bool))); connect(m_ui->versionCheckBox, SIGNAL(toggled(bool)), protocol, SLOT(enableVersionCheck(bool)));
// Logfile
connect(m_ui->logFileButton, SIGNAL(clicked()), this, SLOT(chooseLogfileName()));
// System ID // System ID
connect(protocol, SIGNAL(systemIdChanged(int)), m_ui->systemIdSpinBox, SLOT(setValue(int))); connect(protocol, SIGNAL(systemIdChanged(int)), m_ui->systemIdSpinBox, SLOT(setValue(int)));
connect(m_ui->systemIdSpinBox, SIGNAL(valueChanged(int)), protocol, SLOT(setSystemId(int))); connect(m_ui->systemIdSpinBox, SIGNAL(valueChanged(int)), protocol, SLOT(setSystemId(int)));
...@@ -110,16 +104,10 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget ...@@ -110,16 +104,10 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget
// Update values // Update values
m_ui->versionLabel->setText(tr("MAVLINK_VERSION: %1").arg(protocol->getVersion())); m_ui->versionLabel->setText(tr("MAVLINK_VERSION: %1").arg(protocol->getVersion()));
updateLogfileName(protocol->getLogfileName());
// Connect visibility updates // Connect visibility updates
connect(protocol, SIGNAL(versionCheckChanged(bool)), m_ui->versionLabel, SLOT(setVisible(bool))); connect(protocol, SIGNAL(versionCheckChanged(bool)), m_ui->versionLabel, SLOT(setVisible(bool)));
m_ui->versionLabel->setVisible(protocol->versionCheckEnabled()); 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 // // Multiplexing visibility
// connect(protocol, SIGNAL(multiplexingChanged(bool)), m_ui->multiplexingFilterCheckBox, SLOT(setVisible(bool))); // connect(protocol, SIGNAL(multiplexingChanged(bool)), m_ui->multiplexingFilterCheckBox, SLOT(setVisible(bool)));
// m_ui->multiplexingFilterCheckBox->setVisible(protocol->multiplexingEnabled()); // m_ui->multiplexingFilterCheckBox->setVisible(protocol->multiplexingEnabled());
...@@ -146,39 +134,6 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget ...@@ -146,39 +134,6 @@ MAVLinkSettingsWidget::MAVLinkSettingsWidget(MAVLinkProtocol* protocol, QWidget
m_ui->multiplexingFilterLineEdit->setVisible(false); 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) void MAVLinkSettingsWidget::enableDroneOS(bool enable)
{ {
// Enable multiplexing // Enable multiplexing
......
...@@ -18,10 +18,6 @@ public: ...@@ -18,10 +18,6 @@ public:
~MAVLinkSettingsWidget(); ~MAVLinkSettingsWidget();
public slots: 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 */ /** @brief Enable DroneOS forwarding */
void enableDroneOS(bool enable); void enableDroneOS(bool enable);
......
...@@ -7,35 +7,18 @@ ...@@ -7,35 +7,18 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>431</width> <width>431</width>
<height>530</height> <height>442</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout" columnstretch="1,100,1"> <layout class="QGridLayout" name="gridLayout" columnstretch="1,0,0">
<item row="1" column="0" colspan="3"> <item row="16" column="0">
<widget class="QCheckBox" name="heartbeatCheckBox"> <spacer name="horizontalSpacer_4">
<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">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>8</width> <width>8</width>
...@@ -44,15 +27,8 @@ ...@@ -44,15 +27,8 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="9" column="0" colspan="3"> <item row="13" column="0">
<widget class="QCheckBox" name="versionCheckBox"> <spacer name="horizontalSpacer_3">
<property name="text">
<string>Only accept MAVs with same protocol version</string>
</property>
</widget>
</item>
<item row="10" column="0">
<spacer name="versionSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
...@@ -64,72 +40,28 @@ ...@@ -64,72 +40,28 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="10" column="1" colspan="2"> <item row="11" column="2">
<widget class="QLabel" name="versionLabel"> <widget class="QSpinBox" name="paramRewriteSpinBox">
<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">
<property name="toolTip"> <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>
<property name="statusTip"> <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>
<property name="minimum"> <property name="minimum">
<number>1</number> <number>50</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>255</number> <number>60000</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>
</property> </property>
<property name="text"> <property name="singleStep">
<string>Groundstation MAVLink System ID</string> <number>50</number>
</property> </property>
</widget> </widget>
</item> </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"> <item row="5" column="1" colspan="2">
<widget class="QLineEdit" name="multiplexingFilterLineEdit"> <widget class="QLineEdit" name="multiplexingFilterLineEdit">
<property name="text"> <property name="text">
...@@ -137,28 +69,23 @@ ...@@ -137,28 +69,23 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="3"> <item row="13" column="2">
<widget class="QCheckBox" name="multiplexingFilterCheckBox"> <widget class="QSpinBox" name="actionRetransmissionSpinBox">
<property name="text"> <property name="suffix">
<string>Filter multiplexed packets: Only forward selected IDs</string> <string> ms</string>
</property> </property>
</widget> <property name="minimum">
</item> <number>20</number>
<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>
</widget> <property name="maximum">
</item> <number>1000</number>
<item row="11" column="0" colspan="3"> </property>
<widget class="QCheckBox" name="paramGuardCheckBox"> <property name="singleStep">
<property name="text"> <number>10</number>
<string>Enable retransmission of parameter read/write requests</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="12" column="0"> <item row="10" column="0">
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
...@@ -171,34 +98,7 @@ ...@@ -171,34 +98,7 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="13" column="0"> <item row="10" column="2">
<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">
<widget class="QSpinBox" name="paramRetransmissionSpinBox"> <widget class="QSpinBox" name="paramRetransmissionSpinBox">
<property name="toolTip"> <property name="toolTip">
<string>Time in milliseconds after which a not acknowledged read request is sent again.</string> <string>Time in milliseconds after which a not acknowledged read request is sent again.</string>
...@@ -223,37 +123,41 @@ ...@@ -223,37 +123,41 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="13" column="2"> <item row="14" column="0" colspan="3">
<widget class="QSpinBox" name="paramRewriteSpinBox"> <widget class="Line" name="line">
<property name="toolTip"> <property name="minimumSize">
<string>Time in milliseconds after which a not acknowledged write request is sent again.</string> <size>
</property> <width>0</width>
<property name="statusTip"> <height>0</height>
<string>Time in milliseconds after which a not acknowledged write request is sent again.</string> </size>
</property>
<property name="suffix">
<string> ms</string>
</property>
<property name="minimum">
<number>50</number>
</property> </property>
<property name="maximum"> <property name="midLineWidth">
<number>60000</number> <number>0</number>
</property> </property>
<property name="singleStep"> <property name="orientation">
<number>50</number> <enum>Qt::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="14" column="0" colspan="3"> <item row="17" column="1" colspan="2">
<widget class="QCheckBox" name="actionGuardCheckBox"> <widget class="QComboBox" name="droneOSComboBox">
<property name="text"> <property name="editable">
<string>Enable retransmission of actions / commands</string> <bool>true</bool>
</property> </property>
<item>
<property name="text">
<string>mavlink.droneos.com:14555</string>
</property>
</item>
<item>
<property name="text">
<string>localhost:14555</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item row="15" column="0"> <item row="11" column="0">
<spacer name="horizontalSpacer_3"> <spacer name="horizontalSpacer_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
...@@ -265,57 +169,106 @@ ...@@ -265,57 +169,106 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="15" column="1"> <item row="11" column="1">
<widget class="QLabel" name="actionRetransmissionLabel"> <widget class="QLabel" name="paramRewriteLabel">
<property name="text"> <property name="text">
<string>Action request retransmission timeout</string> <string>Write request retransmission timeout</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="15" column="2"> <item row="10" column="1">
<widget class="QSpinBox" name="actionRetransmissionSpinBox"> <widget class="QLabel" name="paramRetransmissionLabel">
<property name="suffix"> <property name="text">
<string> ms</string> <string>Read request retransmission timeout</string>
</property> </property>
<property name="minimum"> </widget>
<number>20</number> </item>
<item row="1" column="0" colspan="3">
<widget class="QCheckBox" name="heartbeatCheckBox">
<property name="text">
<string>Emit heartbeat</string>
</property> </property>
<property name="maximum"> </widget>
<number>1000</number> </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>
<property name="singleStep"> </widget>
<number>10</number> </item>
<item row="13" column="1">
<widget class="QLabel" name="actionRetransmissionLabel">
<property name="text">
<string>Action request retransmission timeout</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="0" colspan="3"> <item row="8" column="0">
<widget class="Line" name="line"> <spacer name="versionSpacer">
<property name="minimumSize"> <property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size> <size>
<width>0</width> <width>8</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="midLineWidth"> </spacer>
<number>0</number> </item>
<item row="0" column="2">
<widget class="QSpinBox" name="systemIdSpinBox">
<property name="toolTip">
<string>Set the groundstation number</string>
</property> </property>
<property name="orientation"> <property name="statusTip">
<enum>Qt::Horizontal</enum> <string>Set the groundstation number</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>255</number>
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="0" colspan="3"> <item row="15" column="0" colspan="3">
<widget class="QCheckBox" name="droneOSCheckBox"> <widget class="QCheckBox" name="droneOSCheckBox">
<property name="text"> <property name="text">
<string>Forward MAVLink packets of all links to http://droneos.com</string> <string>Forward MAVLink packets of all links to http://droneos.com</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="18" column="0"> <item row="12" column="0" colspan="3">
<spacer name="horizontalSpacer_4"> <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"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>8</width> <width>8</width>
...@@ -324,7 +277,7 @@ ...@@ -324,7 +277,7 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="18" column="1" colspan="2"> <item row="16" column="1" colspan="2">
<widget class="QLineEdit" name="droneOSLineEdit"> <widget class="QLineEdit" name="droneOSLineEdit">
<property name="text"> <property name="text">
<string>Enter your DroneOS API Token/Key</string> <string>Enter your DroneOS API Token/Key</string>
...@@ -334,21 +287,31 @@ ...@@ -334,21 +287,31 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="19" column="1" colspan="2"> <item row="9" column="0" colspan="3">
<widget class="QComboBox" name="droneOSComboBox"> <widget class="QCheckBox" name="paramGuardCheckBox">
<property name="editable"> <property name="text">
<bool>true</bool> <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> </property>
<item>
<property name="text">
<string>mavlink.droneos.com:14555</string>
</property>
</item>
<item>
<property name="text">
<string>localhost:14555</string>
</property>
</item>
</widget> </widget>
</item> </item>
</layout> </layout>
......
...@@ -70,6 +70,7 @@ This file is part of the QGROUNDCONTROL project ...@@ -70,6 +70,7 @@ This file is part of the QGROUNDCONTROL project
#include "menuactionhelper.h" #include "menuactionhelper.h"
#include "QGCUASFileViewMulti.h" #include "QGCUASFileViewMulti.h"
#include <QDesktopWidget> #include <QDesktopWidget>
#include "QGCCore.h"
#ifdef QGC_OSG_ENABLED #ifdef QGC_OSG_ENABLED
#include "Q3DWidgetFactory.h" #include "Q3DWidgetFactory.h"
...@@ -80,41 +81,38 @@ This file is part of the QGROUNDCONTROL project ...@@ -80,41 +81,38 @@ This file is part of the QGROUNDCONTROL project
#include "LogCompressor.h" #include "LogCompressor.h"
static MainWindow* _instance = NULL; ///< @brief MainWindow singleton
// Set up some constants // Set up some constants
const QString MainWindow::defaultDarkStyle = ":files/styles/style-dark.css"; const QString MainWindow::defaultDarkStyle = ":files/styles/style-dark.css";
const QString MainWindow::defaultLightStyle = ":files/styles/style-light.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; Q_ASSERT(_instance == NULL);
if (_instance == 0) Q_ASSERT(splashScreen);
{
_instance = new MainWindow(); new MainWindow(splashScreen, mode);
_instance->setCustomMode(mode);
if (screen) // _instance is set in constructor
{ Q_ASSERT(_instance);
connect(_instance, SIGNAL(initStatusChanged(QString,int,QColor)),
screen, SLOT(showMessage(QString,int,QColor)));
}
_instance->init();
}
return _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;
} }
/** /// @brief Private constructor for MainWindow. MainWindow singleton is only ever created
* Create new mainwindow. The constructor instantiates all parts of the user /// by MainWindow::_create method. Hence no other code should have access to
* interface. It does NOT show the mainwindow. To display it, call the show() /// constructor.
* method. MainWindow::MainWindow(QSplashScreen* splashScreen, enum MainWindow::CUSTOM_MODE mode) :
*
* @see QMainWindow::show()
**/
MainWindow::MainWindow(QWidget *parent):
QMainWindow(parent),
currentView(VIEW_FLIGHT), currentView(VIEW_FLIGHT),
currentStyle(QGC_MAINWINDOW_STYLE_DARK), currentStyle(QGC_MAINWINDOW_STYLE_DARK),
aboutToCloseFlag(false), aboutToCloseFlag(false),
...@@ -124,19 +122,25 @@ MainWindow::MainWindow(QWidget *parent): ...@@ -124,19 +122,25 @@ MainWindow::MainWindow(QWidget *parent):
autoReconnect(false), autoReconnect(false),
simulationLink(NULL), simulationLink(NULL),
lowPowerMode(false), lowPowerMode(false),
customMode(CUSTOM_MODE_NONE), customMode(mode),
menuActionHelper(new MenuActionHelper()) 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); this->setAttribute(Qt::WA_DeleteOnClose);
connect(menuActionHelper, SIGNAL(needToShowDockWidget(QString,bool)),SLOT(showDockWidget(QString,bool))); 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(); loadSettings();
}
void MainWindow::init()
{
emit initStatusChanged(tr("Loading style"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141)); emit initStatusChanged(tr("Loading style"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
qApp->setStyle("plastique"); qApp->setStyle("plastique");
loadStyle(currentStyle); loadStyle(currentStyle);
...@@ -261,11 +265,15 @@ void MainWindow::init() ...@@ -261,11 +265,15 @@ void MainWindow::init()
// Connect link // Connect link
if (autoReconnect) if (autoReconnect)
{ {
LinkManager* linkMgr = LinkManager::instance();
Q_ASSERT(linkMgr);
SerialLink* link = new SerialLink(); SerialLink* link = new SerialLink();
// Add to registry // Add to registry
LinkManager::instance()->add(link); linkMgr->add(link);
LinkManager::instance()->addProtocol(link, mavlink); linkMgr->addProtocol(link, mavlink);
link->connect(); linkMgr->connectLink(link);
} }
// Set low power mode // Set low power mode
...@@ -1122,24 +1130,16 @@ void MainWindow::showStatusMessage(const QString& status) ...@@ -1122,24 +1130,16 @@ void MainWindow::showStatusMessage(const QString& status)
void MainWindow::showCriticalMessage(const QString& title, const QString& message) void MainWindow::showCriticalMessage(const QString& title, const QString& message)
{ {
QMessageBox msgBox(this); _hideSplashScreen();
msgBox.setIcon(QMessageBox::Critical); qDebug() << "Critical" << title << message;
msgBox.setText(title); QMessageBox::critical(this, title, message);
msgBox.setInformativeText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
} }
void MainWindow::showInfoMessage(const QString& title, const QString& message) void MainWindow::showInfoMessage(const QString& title, const QString& message)
{ {
QMessageBox msgBox(this); _hideSplashScreen();
msgBox.setIcon(QMessageBox::Information); qDebug() << "Information" << title << message;
msgBox.setText(title); QMessageBox::information(this, title, message);
msgBox.setInformativeText(message);
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
} }
/** /**
...@@ -1276,52 +1276,37 @@ void MainWindow::connectCommonActions() ...@@ -1276,52 +1276,37 @@ void MainWindow::connectCommonActions()
connect(ui.actionSimulate, SIGNAL(triggered(bool)), this, SLOT(simulateLink(bool))); 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"))) if(!QDesktopServices::openUrl(QUrl(url))) {
{ QMessageBox::critical(this,
QMessageBox msgBox; tr("Could not open information in browser"),
msgBox.setIcon(QMessageBox::Critical); errorMessage);
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();
} }
} }
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() void MainWindow::showCredits()
{ {
if(!QDesktopServices::openUrl(QUrl("http://qgroundcontrol.org/credits"))) _openUrl("http://qgroundcontrol.org/credits",
{ tr("To get to the credits, please open http://qgroundcontrol.org/credits in a browser."));
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();
}
} }
void MainWindow::showRoadMap() void MainWindow::showRoadMap()
{ {
if(!QDesktopServices::openUrl(QUrl("http://qgroundcontrol.org/dev/roadmap"))) _openUrl("http://qgroundcontrol.org/dev/roadmap",
{ tr("To get to the online help, please open http://qgroundcontrol.org/roadmap in a browser."));
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();
}
} }
void MainWindow::showSettings() void MainWindow::showSettings()
{ {
QGCSettingsWidget* settings = new QGCSettingsWidget(joystick, this); SettingsDialog settings(joystick, this);
settings->show(); settings.exec();
} }
LinkInterface* MainWindow::addLink() LinkInterface* MainWindow::addLink()
...@@ -1412,9 +1397,16 @@ void MainWindow::addLink(LinkInterface *link) ...@@ -1412,9 +1397,16 @@ void MainWindow::addLink(LinkInterface *link)
} }
void MainWindow::simulateLink(bool simulate) { void MainWindow::simulateLink(bool simulate) {
if (!simulationLink) if (simulate) {
simulationLink = new MAVLinkSimulationLink(":/demo-log.txt"); if (!simulationLink) {
simulationLink->connectLink(simulate); 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) void MainWindow::commsWidgetDestroyed(QObject *obj)
...@@ -1700,6 +1692,9 @@ void MainWindow::handleMisconfiguration(UASInterface* uas) ...@@ -1700,6 +1692,9 @@ void MainWindow::handleMisconfiguration(UASInterface* uas)
return; return;
} }
} }
_hideSplashScreen();
// Ask user if he wants to handle this now // Ask user if he wants to handle this now
QMessageBox msgBox(this); QMessageBox msgBox(this);
msgBox.setIcon(QMessageBox::Information); msgBox.setIcon(QMessageBox::Information);
...@@ -1826,6 +1821,31 @@ bool MainWindow::dockWidgetTitleBarsEnabled() const ...@@ -1826,6 +1821,31 @@ bool MainWindow::dockWidgetTitleBarsEnabled() const
return menuActionHelper->dockWidgetTitleBarsEnabled(); 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 #ifdef QGC_MOUSE_ENABLED_LINUX
bool MainWindow::x11Event(XEvent *event) bool MainWindow::x11Event(XEvent *event)
{ {
......
...@@ -100,31 +100,18 @@ public: ...@@ -100,31 +100,18 @@ public:
CUSTOM_MODE_WIFI CUSTOM_MODE_WIFI
}; };
/** /// @brief Returns the MainWindow singleton. Will not create the MainWindow if it has not already
* A static function for obtaining the sole instance of the MainWindow. The screen /// been created.
* argument is only important on the FIRST call to this function. The provided splash static MainWindow* instance(void);
* screen is updated with some status messages that are emitted during init(). This
* function cannot be used within the MainWindow constructor! /// @brief Creates the MainWindow singleton. Should only be called once by QGCApplication.
*/ static MainWindow* _create(QSplashScreen* splashScreen, enum MainWindow::CUSTOM_MODE mode);
static MainWindow* instance(QSplashScreen* screen = 0);
static MainWindow* instance_mode(QSplashScreen* screen = 0, enum MainWindow::CUSTOM_MODE mode = MainWindow::CUSTOM_MODE_NONE); /// @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(); ~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 enum QGC_MAINWINDOW_STYLE
{ {
QGC_MAINWINDOW_STYLE_DARK, QGC_MAINWINDOW_STYLE_DARK,
...@@ -272,7 +259,7 @@ public slots: ...@@ -272,7 +259,7 @@ public slots:
void configureWindowName(); void configureWindowName();
void commsWidgetDestroyed(QObject *obj); void commsWidgetDestroyed(QObject *obj);
protected slots: protected slots:
void showDockWidget(const QString &name, bool show); void showDockWidget(const QString &name, bool show);
/** /**
...@@ -475,8 +462,18 @@ protected: ...@@ -475,8 +462,18 @@ protected:
QGCFlightGearLink* fgLink; QGCFlightGearLink* fgLink;
QTimer windowNameUpdateTimer; QTimer windowNameUpdateTimer;
CUSTOM_MODE customMode; CUSTOM_MODE customMode;
private slots:
/// @brief Save the specified Flight Data Log
void _saveTempFlightDataLog(QString tempLogfile);
private: 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; QList<QObject*> commsWidgetList;
QMap<QString,QString> customWidgetNameToFilenameMap; QMap<QString,QString> customWidgetNameToFilenameMap;
MenuActionHelper *menuActionHelper; MenuActionHelper *menuActionHelper;
...@@ -489,6 +486,8 @@ private: ...@@ -489,6 +486,8 @@ private:
QString getWindowStateKey(); QString getWindowStateKey();
QString getWindowGeometryKey(); QString getWindowGeometryKey();
QSplashScreen* _splashScreen; ///< Splash screen, NULL is splash screen not currently being shown
friend class MenuActionHelper; //For VIEW_SECTIONS friend class MenuActionHelper; //For VIEW_SECTIONS
}; };
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include "QGCMAVLinkLogPlayer.h" #include "QGCMAVLinkLogPlayer.h"
#include "QGC.h" #include "QGC.h"
#include "ui_QGCMAVLinkLogPlayer.h" #include "ui_QGCMAVLinkLogPlayer.h"
#include "QGCCore.h"
#include "LinkManager.h"
QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *parent) : QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *parent) :
QWidget(parent), QWidget(parent),
...@@ -22,9 +24,10 @@ QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *pare ...@@ -22,9 +24,10 @@ QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *pare
binaryBaudRate(defaultBinaryBaudRate), binaryBaudRate(defaultBinaryBaudRate),
isPlaying(false), isPlaying(false),
currPacketCount(0), currPacketCount(0),
lastLogDirectory(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)),
ui(new Ui::QGCMAVLinkLogPlayer) ui(new Ui::QGCMAVLinkLogPlayer)
{ {
Q_ASSERT(mavlink);
ui->setupUi(this); ui->setupUi(this);
ui->horizontalLayout->setAlignment(Qt::AlignTop); ui->horizontalLayout->setAlignment(Qt::AlignTop);
...@@ -32,14 +35,18 @@ QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *pare ...@@ -32,14 +35,18 @@ QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *pare
connect(this, SIGNAL(bytesReady(LinkInterface*,QByteArray)), mavlink, SLOT(receiveBytes(LinkInterface*,QByteArray))); connect(this, SIGNAL(bytesReady(LinkInterface*,QByteArray)), mavlink, SLOT(receiveBytes(LinkInterface*,QByteArray)));
// Setup timer // Setup timer
connect(&loopTimer, SIGNAL(timeout()), this, SLOT(logLoop())); connect(&loopTimer, &QTimer::timeout, this, &QGCMAVLinkLogPlayer::logLoop);
// Setup buttons // Setup buttons
connect(ui->selectFileButton, SIGNAL(clicked()), this, SLOT(selectLogFile())); connect(ui->selectFileButton, &QPushButton::clicked, this, &QGCMAVLinkLogPlayer::_selectLogFileForPlayback);
connect(ui->playButton, SIGNAL(clicked()), this, SLOT(playPauseToggle())); connect(ui->playButton, &QPushButton::clicked, this, &QGCMAVLinkLogPlayer::playPauseToggle);
connect(ui->speedSlider, SIGNAL(valueChanged(int)), this, SLOT(setAccelerationFactorInt(int))); connect(ui->speedSlider, &QSlider::valueChanged, this, &QGCMAVLinkLogPlayer::setAccelerationFactorInt);
connect(ui->positionSlider, SIGNAL(valueChanged(int)), this, SLOT(jumpToSliderVal(int))); connect(ui->positionSlider, &QSlider::valueChanged, this, &QGCMAVLinkLogPlayer::jumpToSliderVal);
connect(ui->positionSlider, SIGNAL(sliderPressed()), this, SLOT(pause())); 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); setAccelerationFactorInt(49);
ui->speedSlider->setValue(49); ui->speedSlider->setValue(49);
...@@ -53,29 +60,14 @@ QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *pare ...@@ -53,29 +60,14 @@ QGCMAVLinkLogPlayer::QGCMAVLinkLogPlayer(MAVLinkProtocol* mavlink, QWidget *pare
ui->logStatsLabel->setEnabled(false); 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. // 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())); connect(this, &QGCMAVLinkLogPlayer::logFileEndReached, &loopTimer, &QTimer::stop);
loadSettings();
} }
QGCMAVLinkLogPlayer::~QGCMAVLinkLogPlayer() QGCMAVLinkLogPlayer::~QGCMAVLinkLogPlayer()
{ {
storeSettings();
delete ui; delete ui;
} }
void QGCMAVLinkLogPlayer::playPause(bool enabled)
{
if (enabled)
{
play();
}
else
{
pause();
}
}
void QGCMAVLinkLogPlayer::playPauseToggle() void QGCMAVLinkLogPlayer::playPauseToggle()
{ {
if (isPlaying) if (isPlaying)
...@@ -90,55 +82,49 @@ void QGCMAVLinkLogPlayer::playPauseToggle() ...@@ -90,55 +82,49 @@ void QGCMAVLinkLogPlayer::playPauseToggle()
void QGCMAVLinkLogPlayer::play() 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 reset();
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();
}
// Always correct the current start time such that the next message will play immediately at playback. // 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() // We do this by subtracting the current file playback offset from now()
playbackStartTime = (quint64)QDateTime::currentMSecsSinceEpoch() - (logCurrentTime - logStartTime) / 1000; playbackStartTime = (quint64)QDateTime::currentMSecsSinceEpoch() - (logCurrentTime - logStartTime) / 1000;
// Start timer // Start timer
if (mavlinkLogFormat) if (mavlinkLogFormat)
{ {
loopTimer.start(1); 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"));
} }
else else
{ {
ui->playButton->setChecked(false); // Read len bytes at a time
QMessageBox msgBox; int len = 100;
msgBox.setIcon(QMessageBox::Information); // Calculate the number of times to read 100 bytes per second
msgBox.setText(tr("No logfile selected")); // to guarantee the baud rate, then divide 1000 by the number of read
msgBox.setInformativeText(tr("Please select first a MAVLink log file before playing it.")); // operations to obtain the interval in milliseconds
msgBox.setStandardButtons(QMessageBox::Ok); int interval = 1000 / ((binaryBaudRate / 10) / len);
msgBox.setDefaultButton(QMessageBox::Ok); loopTimer.start(interval / accelerationFactor);
msgBox.exec();
} }
isPlaying = true;
ui->playButton->setChecked(true);
ui->playButton->setIcon(QIcon(":files/images/actions/media-playback-pause.svg"));
} }
void QGCMAVLinkLogPlayer::pause() void QGCMAVLinkLogPlayer::pause()
{ {
LinkManager::instance()->setConnectionsAllowed();
emit suspendLogForReplay(false);
loopTimer.stop(); loopTimer.stop();
isPlaying = false; isPlaying = false;
ui->playButton->setIcon(QIcon(":files/images/actions/media-playback-start.svg")); ui->playButton->setIcon(QIcon(":files/images/actions/media-playback-start.svg"));
...@@ -258,51 +244,33 @@ void QGCMAVLinkLogPlayer::updatePositionSliderUi(float percent) ...@@ -258,51 +244,33 @@ void QGCMAVLinkLogPlayer::updatePositionSliderUi(float percent)
ui->positionSlider->blockSignals(false); 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; // Disallow replay when any links are connected
settings.beginGroup("QGC_MAVLINKLOGPLAYER");
lastLogDirectory = settings.value("LAST_LOG_DIRECTORY", lastLogDirectory).toString(); bool foundConnection = false;
settings.endGroup(); LinkManager* linkMgr = LinkManager::instance();
} QList<LinkInterface*> links = linkMgr->getLinks();
foreach(LinkInterface* link, links) {
void QGCMAVLinkLogPlayer::storeSettings() if (link->isConnected()) {
{ foundConnection = true;
QSettings settings; break;
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;
} }
else
{ if (foundConnection) {
lastLogDirectory = fileName; MainWindow::instance()->showInfoMessage(tr("Log Replay"), tr("You must close all connections prior to replaying a log."));
return loadLogFile(fileName); 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) ...@@ -341,157 +309,125 @@ void QGCMAVLinkLogPlayer::setAccelerationFactorInt(int factor)
bool QGCMAVLinkLogPlayer::loadLogFile(const QString& file) 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. // Make sure to stop the logging process and reset everything.
reset(); reset();
logFile.close();
// And that the old file is closed nicely.
if (logFile.isOpen())
{
logFile.close();
}
// Now load the new file. // Now load the new file.
logFile.setFileName(file); logFile.setFileName(file);
if (!logFile.open(QFile::ReadOnly)) 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));
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)); _playbackError();
logFile.setFileName("");
return false; return false;
} }
else
{ QFileInfo logFileInfo(file);
QFileInfo logFileInfo(file); ui->logFileNameLabel->setText(tr("File: %1").arg(logFileInfo.fileName()));
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("");
// 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 // Select if binary or MAVLink log format is used
mavlinkLogFormat = file.endsWith(".mavlink"); 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;
}
if (endtime == starttime) { if (mavlinkLogFormat)
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(""); // Get the first timestamp from the logfile
ui->logFileNameLabel->setText(tr("No logfile selected")); // This should be a big-endian uint64.
return false; 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. if (endtime == starttime) {
logEndTime = endtime; MainWindow::instance()->showCriticalMessage(tr("The selected file is corrupt"), tr("No valid timestamps were found at the end of the file.").arg(file));
logStartTime = starttime; _playbackError();
logCurrentTime = logStartTime; return false;
}
// Reset our log file so when we go to read it for the first time, we start at the beginning. // Remember the start and end time so we can move around this logfile with the slider.
logFile.reset(); logEndTime = endtime;
logStartTime = starttime;
logCurrentTime = logStartTime;
// Calculate the runtime in hours:minutes:seconds // Reset our log file so when we go to read it for the first time, we start at the beginning.
// WARNING: Order matters in this computation logFile.reset();
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. // Calculate the runtime in hours:minutes:seconds
QString timelabel = tr("%1h:%2m:%3s").arg(hours, 2).arg(minutes, 2).arg(seconds, 2); // WARNING: Order matters in this computation
currPacketCount = logFileInfo.size()/(32 + MAVLINK_NUM_NON_PAYLOAD_BYTES + sizeof(quint64)); // Count packets by assuming an average payload size of 32 bytes quint32 seconds = (endtime - starttime)/1000000;
ui->logStatsLabel->setText(tr("%2 MB, ~%3 packets, %4").arg(logFileInfo.size()/1000000.0f, 0, 'f', 2).arg(currPacketCount).arg(timelabel)); quint32 minutes = seconds / 60;
} quint32 hours = minutes / 60;
else 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 bool ok;
// of the baud rate they were recorded at, like `test_run_115200.bin`. Then on int rate = parts.last().toInt(&ok);
// playback, the datarate is equal to set to this value. // 9600 baud to 100 MBit
if (ok && (rate > 9600 && rate < 100000000))
// Set baud rate if any present. Otherwise we default to 57600.
QStringList parts = logFileInfo.baseName().split("_");
binaryBaudRate = defaultBinaryBaudRate;
if (parts.count() > 1)
{ {
bool ok; // Accept this as valid baudrate
int rate = parts.last().toInt(&ok); binaryBaudRate = rate;
// 9600 baud to 100 MBit
if (ok && (rate > 9600 && rate < 100000000))
{
// 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 int seconds = logFileInfo.size() / (binaryBaudRate / 10);
int minutes = seconds / 60;
bool linkWarning = false; int hours = minutes / 60;
foreach (LinkInterface* link, LinkManager::instance()->getLinks()) seconds -= 60*minutes;
{ minutes -= 60*hours;
SerialLink* s = dynamic_cast<SerialLink*>(link);
if (s && s->isConnected())
linkWarning = true;
}
if (linkWarning) QString timelabel = tr("%1h:%2m:%3s").arg(hours, 2).arg(minutes, 2).arg(seconds, 2);
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.")); 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) quint64 QGCMAVLinkLogPlayer::parseTimestamp(const QByteArray &data)
...@@ -593,17 +529,8 @@ void QGCMAVLinkLogPlayer::logLoop() ...@@ -593,17 +529,8 @@ void QGCMAVLinkLogPlayer::logLoop()
emit bytesReady(logLink, message); emit bytesReady(logLink, message);
// If we've reached the end of the of the file, make sure we handle that well // If we've reached the end of the of the file, make sure we handle that well
if (logFile.atEnd()) if (logFile.atEnd()) {
{ _finishPlayback();
// 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();
return; return;
} }
...@@ -633,12 +560,7 @@ void QGCMAVLinkLogPlayer::logLoop() ...@@ -633,12 +560,7 @@ void QGCMAVLinkLogPlayer::logLoop()
// Check if reached end of file before reading next timestamp // Check if reached end of file before reading next timestamp
if (chunk.length() < len || logFile.atEnd()) if (chunk.length() < len || logFile.atEnd())
{ {
// Reached end of file _finishPlayback();
reset();
QString status = tr("Reached end of binary log file.");
ui->logStatsLabel->setText(status);
MainWindow::instance()->showStatusMessage(status);
return; return;
} }
} }
...@@ -707,3 +629,40 @@ void QGCMAVLinkLogPlayer::paintEvent(QPaintEvent *) ...@@ -707,3 +629,40 @@ void QGCMAVLinkLogPlayer::paintEvent(QPaintEvent *)
QPainter p(this); QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &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: ...@@ -37,29 +37,15 @@ public:
return logFile.isOpen(); return logFile.isOpen();
} }
/**
* @brief Set the last log file name
* @param filename
*/
void setLastLogFile(const QString& filename) {
lastLogDirectory = filename;
}
public slots: public slots:
/** @brief Toggle between play and pause */ /** @brief Toggle between play and pause */
void playPauseToggle(); void playPauseToggle();
/** @brief Play / pause the log */
void playPause(bool play);
/** @brief Replay the logfile */ /** @brief Replay the logfile */
void play(); void play();
/** @brief Pause the log player. */ /** @brief Pause the log player. */
void pause(); void pause();
/** @brief Reset the internal log player state, including the UI */ /** @brief Reset the internal log player state, including the UI */
void reset(); void reset();
/** @brief Select logfile */
bool selectLogFile(const QString startDirectory);
/** @brief Select logfile */
bool selectLogFile();
/** @brief Load log file */ /** @brief Load log file */
bool loadLogFile(const QString& file); bool loadLogFile(const QString& file);
/** @brief Jump to a position in the logfile */ /** @brief Jump to a position in the logfile */
...@@ -73,6 +59,9 @@ signals: ...@@ -73,6 +59,9 @@ signals:
/** @brief Send ready bytes */ /** @brief Send ready bytes */
void bytesReady(LinkInterface* link, const QByteArray& bytes); void bytesReady(LinkInterface* link, const QByteArray& bytes);
void logFileEndReached(); void logFileEndReached();
/// @brief Connected to the MAVLinkProtocol::suspendLogForReplay
void suspendLogForReplay(bool suspend);
protected: 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. 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: ...@@ -92,11 +81,10 @@ protected:
unsigned int currPacketCount; unsigned int currPacketCount;
static const int packetLen = MAVLINK_MAX_PACKET_LEN; static const int packetLen = MAVLINK_MAX_PACKET_LEN;
static const int timeLen = sizeof(quint64); static const int timeLen = sizeof(quint64);
QString lastLogDirectory;
void changeEvent(QEvent *e); void changeEvent(QEvent *e);
void loadSettings(); private slots:
void storeSettings(); void _selectLogFileForPlayback(void);
private: private:
Ui::QGCMAVLinkLogPlayer *ui; Ui::QGCMAVLinkLogPlayer *ui;
...@@ -130,6 +118,9 @@ private: ...@@ -130,6 +118,9 @@ private:
* @return True if the new file position was successfully jumped to, false otherwise * @return True if the new file position was successfully jumped to, false otherwise
*/ */
bool jumpToPlaybackLocation(float percentage); bool jumpToPlaybackLocation(float percentage);
void _finishPlayback(void);
void _playbackError(void);
}; };
#endif // QGCMAVLINKLOGPLAYER_H #endif // QGCMAVLINKLOGPLAYER_H
...@@ -36,13 +36,13 @@ ...@@ -36,13 +36,13 @@
<item> <item>
<widget class="QToolButton" name="playButton"> <widget class="QToolButton" name="playButton">
<property name="toolTip"> <property name="toolTip">
<string>Start to replay the logfile</string> <string>Start to replay Flight Data</string>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>Start to replay the logfile</string> <string>Start to replay Flight Data</string>
</property> </property>
<property name="whatsThis"> <property name="whatsThis">
<string>Start to replay the logfile</string> <string>Start to replay Flight Data</string>
</property> </property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
...@@ -129,23 +129,23 @@ ...@@ -129,23 +129,23 @@
<item> <item>
<widget class="QLabel" name="logFileNameLabel"> <widget class="QLabel" name="logFileNameLabel">
<property name="text"> <property name="text">
<string>No logfile selected..</string> <string>No Flight Data selected..</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="selectFileButton"> <widget class="QPushButton" name="selectFileButton">
<property name="toolTip"> <property name="toolTip">
<string>Select the logfile to replay</string> <string>Select the Flight Data to replay</string>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>Select the logfile to replay</string> <string>Select the Flight Data to replay</string>
</property> </property>
<property name="whatsThis"> <property name="whatsThis">
<string>Select the logfile to replay</string> <string>Select the Flight Data to replay</string>
</property> </property>
<property name="text"> <property name="text">
<string>Replay Logfile</string> <string>Replay Flight Data</string>
</property> </property>
</widget> </widget>
</item> </item>
......
...@@ -27,21 +27,15 @@ This file is part of the QGROUNDCONTROL project ...@@ -27,21 +27,15 @@ This file is part of the QGROUNDCONTROL project
#include "QGCStatusBar.h" #include "QGCStatusBar.h"
#include "UASManager.h" #include "UASManager.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "QGCCore.h"
QGCStatusBar::QGCStatusBar(QWidget *parent) : QGCStatusBar::QGCStatusBar(QWidget *parent) :
QStatusBar(parent), QStatusBar(parent),
toggleLoggingButton(NULL),
player(NULL), player(NULL),
changed(true), changed(true),
lastLogDirectory(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)) lastLogDirectory(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation))
{ {
setObjectName("QGC_STATUSBAR"); setObjectName("QGC_STATUSBAR");
toggleLoggingButton = new QPushButton(tr("Log to file"), this);
toggleLoggingButton->setCheckable(true);
addPermanentWidget(toggleLoggingButton);
loadSettings(); loadSettings();
} }
...@@ -58,58 +52,6 @@ void QGCStatusBar::setLogPlayer(QGCMAVLinkLogPlayer* player) ...@@ -58,58 +52,6 @@ void QGCStatusBar::setLogPlayer(QGCMAVLinkLogPlayer* player)
{ {
this->player = player; this->player = player;
addPermanentWidget(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() void QGCStatusBar::loadSettings()
...@@ -132,5 +74,4 @@ void QGCStatusBar::storeSettings() ...@@ -132,5 +74,4 @@ void QGCStatusBar::storeSettings()
QGCStatusBar::~QGCStatusBar() QGCStatusBar::~QGCStatusBar()
{ {
storeSettings(); storeSettings();
if (toggleLoggingButton) toggleLoggingButton->deleteLater();
} }
...@@ -43,8 +43,6 @@ public: ...@@ -43,8 +43,6 @@ public:
~QGCStatusBar(); ~QGCStatusBar();
public slots: public slots:
/** @brief Start / stop logging */
void logging(bool checked);
/** @brief Set log playing component */ /** @brief Set log playing component */
void setLogPlayer(QGCMAVLinkLogPlayer* player); void setLogPlayer(QGCMAVLinkLogPlayer* player);
virtual void paintEvent(QPaintEvent * event); virtual void paintEvent(QPaintEvent * event);
...@@ -53,7 +51,6 @@ protected: ...@@ -53,7 +51,6 @@ protected:
void storeSettings(); void storeSettings();
void loadSettings(); void loadSettings();
QPushButton* toggleLoggingButton;
QGCMAVLinkLogPlayer* player; QGCMAVLinkLogPlayer* player;
bool changed; bool changed;
QString lastLogDirectory; 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