mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2025-02-21 11:54:15 +01:00
Merged in Oblivium/librepilot/LP-597_Progress_bar_for_GCS_log_replay (pull request #511)
LP-597 Progress bar for GCS log replay Approved-by: Jan NIJS <dr.oblivium@gmail.com> Approved-by: Philippe Renon <philippe_renon@yahoo.fr> Approved-by: Lalanne Laurent <f5soh@free.fr>
This commit is contained in:
commit
2f075df628
@ -2,7 +2,7 @@
|
|||||||
******************************************************************************
|
******************************************************************************
|
||||||
*
|
*
|
||||||
* @file logfile.cpp
|
* @file logfile.cpp
|
||||||
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017.
|
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017-2018.
|
||||||
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||||
* @see The GNU Public License (GPL) Version 3
|
* @see The GNU Public License (GPL) Version 3
|
||||||
*
|
*
|
||||||
@ -26,6 +26,9 @@
|
|||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
#include <QDataStream>
|
||||||
|
|
||||||
|
#define TIMESTAMP_SIZE_BYTES 4
|
||||||
|
|
||||||
LogFile::LogFile(QObject *parent) : QIODevice(parent),
|
LogFile::LogFile(QObject *parent) : QIODevice(parent),
|
||||||
m_timer(this),
|
m_timer(this),
|
||||||
@ -34,9 +37,12 @@ LogFile::LogFile(QObject *parent) : QIODevice(parent),
|
|||||||
m_lastPlayed(0),
|
m_lastPlayed(0),
|
||||||
m_timeOffset(0),
|
m_timeOffset(0),
|
||||||
m_playbackSpeed(1.0),
|
m_playbackSpeed(1.0),
|
||||||
paused(false),
|
m_replayState(STOPPED),
|
||||||
m_useProvidedTimeStamp(false),
|
m_useProvidedTimeStamp(false),
|
||||||
m_providedTimeStamp(0)
|
m_providedTimeStamp(0),
|
||||||
|
m_beginTimeStamp(0),
|
||||||
|
m_endTimeStamp(0),
|
||||||
|
m_timerTick(0)
|
||||||
{
|
{
|
||||||
connect(&m_timer, &QTimer::timeout, this, &LogFile::timerFired);
|
connect(&m_timer, &QTimer::timeout, this, &LogFile::timerFired);
|
||||||
}
|
}
|
||||||
@ -137,36 +143,61 @@ qint64 LogFile::bytesAvailable() const
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
timerFired()
|
||||||
|
|
||||||
|
This function is called at a 10 ms interval to fill the replay buffers.
|
||||||
|
|
||||||
|
*/
|
||||||
void LogFile::timerFired()
|
void LogFile::timerFired()
|
||||||
{
|
{
|
||||||
if (m_file.bytesAvailable() > 4) {
|
if (m_replayState != PLAYING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_timerTick++;
|
||||||
|
|
||||||
|
if (m_file.bytesAvailable() > TIMESTAMP_SIZE_BYTES) {
|
||||||
int time;
|
int time;
|
||||||
time = m_myTime.elapsed();
|
time = m_myTime.elapsed();
|
||||||
|
|
||||||
// TODO: going back in time will be a problem
|
/*
|
||||||
while ((m_lastPlayed + ((double)(time - m_timeOffset) * m_playbackSpeed) > m_nextTimeStamp)) {
|
This code generates an advancing playback window. All samples that fit the window
|
||||||
|
are replayed. The window is about the size of the timer interval: 10 ms.
|
||||||
|
|
||||||
|
Description of used variables:
|
||||||
|
|
||||||
|
time : real-time interval since start of playback (in ms) - now()
|
||||||
|
m_timeOffset : real-time interval since start of playback (in ms) - when timerFired() was previously run
|
||||||
|
m_nextTimeStamp : read log until this log timestamp has been reached (in ms)
|
||||||
|
m_lastPlayed : log referenced timestamp advanced to during previous cycle (in ms)
|
||||||
|
m_playbackSpeed : 0.1 .. 1.0 .. 10 replay speedup factor
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
while (m_nextTimeStamp < (m_lastPlayed + (double)(time - m_timeOffset) * m_playbackSpeed)) {
|
||||||
|
// advance the replay window for the next time period
|
||||||
m_lastPlayed += ((double)(time - m_timeOffset) * m_playbackSpeed);
|
m_lastPlayed += ((double)(time - m_timeOffset) * m_playbackSpeed);
|
||||||
|
|
||||||
// read data size
|
// read data size
|
||||||
qint64 dataSize;
|
qint64 dataSize;
|
||||||
if (m_file.bytesAvailable() < (qint64)sizeof(dataSize)) {
|
if (m_file.bytesAvailable() < (qint64)sizeof(dataSize)) {
|
||||||
qDebug() << "LogFile - end of log file reached";
|
qDebug() << "LogFile replay - end of log file reached";
|
||||||
stopReplay();
|
resetReplay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_file.read((char *)&dataSize, sizeof(dataSize));
|
m_file.read((char *)&dataSize, sizeof(dataSize));
|
||||||
|
|
||||||
// check size consistency
|
// check size consistency
|
||||||
if (dataSize < 1 || dataSize > (1024 * 1024)) {
|
if (dataSize < 1 || dataSize > (1024 * 1024)) {
|
||||||
qWarning() << "LogFile - corrupted log file! Unlikely packet size:" << dataSize;
|
qWarning() << "LogFile replay - corrupted log file! Unlikely packet size:" << dataSize;
|
||||||
stopReplay();
|
stopReplay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// read data
|
// read data
|
||||||
if (m_file.bytesAvailable() < dataSize) {
|
if (m_file.bytesAvailable() < dataSize) {
|
||||||
qDebug() << "LogFile - end of log file reached";
|
qDebug() << "LogFile replay - end of log file reached";
|
||||||
stopReplay();
|
resetReplay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QByteArray data = m_file.read(dataSize);
|
QByteArray data = m_file.read(dataSize);
|
||||||
@ -178,10 +209,14 @@ void LogFile::timerFired()
|
|||||||
|
|
||||||
emit readyRead();
|
emit readyRead();
|
||||||
|
|
||||||
|
// rate-limit slider bar position updates to 10 updates per second
|
||||||
|
if (m_timerTick % 10 == 0) {
|
||||||
|
emit playbackPositionChanged(m_nextTimeStamp);
|
||||||
|
}
|
||||||
// read next timestamp
|
// read next timestamp
|
||||||
if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
|
if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
|
||||||
qDebug() << "LogFile - end of log file reached";
|
qDebug() << "LogFile replay - end of log file reached";
|
||||||
stopReplay();
|
resetReplay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_previousTimeStamp = m_nextTimeStamp;
|
m_previousTimeStamp = m_nextTimeStamp;
|
||||||
@ -190,17 +225,17 @@ void LogFile::timerFired()
|
|||||||
// some validity checks
|
// some validity checks
|
||||||
if ((m_nextTimeStamp < m_previousTimeStamp) // logfile goes back in time
|
if ((m_nextTimeStamp < m_previousTimeStamp) // logfile goes back in time
|
||||||
|| ((m_nextTimeStamp - m_previousTimeStamp) > 60 * 60 * 1000)) { // gap of more than 60 minutes
|
|| ((m_nextTimeStamp - m_previousTimeStamp) > 60 * 60 * 1000)) { // gap of more than 60 minutes
|
||||||
qWarning() << "LogFile - corrupted log file! Unlikely timestamp:" << m_nextTimeStamp << "after" << m_previousTimeStamp;
|
qWarning() << "LogFile replay - corrupted log file! Unlikely timestamp:" << m_nextTimeStamp << "after" << m_previousTimeStamp;
|
||||||
stopReplay();
|
stopReplay();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_timeOffset = time;
|
m_timeOffset = time;
|
||||||
time = m_myTime.elapsed();
|
time = m_myTime.elapsed(); // number of milliseconds since start of playback
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "LogFile - end of log file reached";
|
qDebug() << "LogFile replay - end of log file reached";
|
||||||
stopReplay();
|
resetReplay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,8 +244,26 @@ bool LogFile::isPlaying() const
|
|||||||
return m_file.isOpen() && m_timer.isActive();
|
return m_file.isOpen() && m_timer.isActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FUNCTION: startReplay()
|
||||||
|
*
|
||||||
|
* Starts replaying a newly opened logfile.
|
||||||
|
* Starts a timer: m_timer
|
||||||
|
*
|
||||||
|
* This function and the stopReplay() function should only ever be called from the same thread.
|
||||||
|
* This is required for correct control of the timer.
|
||||||
|
*
|
||||||
|
*/
|
||||||
bool LogFile::startReplay()
|
bool LogFile::startReplay()
|
||||||
{
|
{
|
||||||
|
// Walk through logfile and create timestamp index
|
||||||
|
// Don't start replay if there was a problem indexing the logfile.
|
||||||
|
if (!buildIndex()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_timerTick = 0;
|
||||||
|
|
||||||
if (!m_file.isOpen() || m_timer.isActive()) {
|
if (!m_file.isOpen() || m_timer.isActive()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -221,7 +274,9 @@ bool LogFile::startReplay()
|
|||||||
m_lastPlayed = 0;
|
m_lastPlayed = 0;
|
||||||
m_previousTimeStamp = 0;
|
m_previousTimeStamp = 0;
|
||||||
m_nextTimeStamp = 0;
|
m_nextTimeStamp = 0;
|
||||||
|
m_mutex.lock();
|
||||||
m_dataBuffer.clear();
|
m_dataBuffer.clear();
|
||||||
|
m_mutex.unlock();
|
||||||
|
|
||||||
// read next timestamp
|
// read next timestamp
|
||||||
if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
|
if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
|
||||||
@ -232,25 +287,125 @@ bool LogFile::startReplay()
|
|||||||
|
|
||||||
m_timer.setInterval(10);
|
m_timer.setInterval(10);
|
||||||
m_timer.start();
|
m_timer.start();
|
||||||
paused = false;
|
m_replayState = PLAYING;
|
||||||
|
|
||||||
emit replayStarted();
|
emit replayStarted();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FUNCTION: stopReplay()
|
||||||
|
*
|
||||||
|
* Stops replaying the logfile.
|
||||||
|
* Stops the timer: m_timer
|
||||||
|
*
|
||||||
|
* This function and the startReplay() function should only ever be called from the same thread.
|
||||||
|
* This is a requirement to be able to control the timer.
|
||||||
|
*
|
||||||
|
*/
|
||||||
bool LogFile::stopReplay()
|
bool LogFile::stopReplay()
|
||||||
{
|
{
|
||||||
if (!m_file.isOpen() || !(m_timer.isActive() || paused)) {
|
if (!m_file.isOpen()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (m_timer.isActive()) {
|
||||||
|
m_timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "LogFile - stopReplay";
|
qDebug() << "LogFile - stopReplay";
|
||||||
m_timer.stop();
|
m_replayState = STOPPED;
|
||||||
paused = false;
|
|
||||||
|
|
||||||
emit replayFinished();
|
emit replayFinished();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FUNCTION: resetReplay()
|
||||||
|
*
|
||||||
|
* Stops replaying the logfile.
|
||||||
|
* Stops the timer: m_timer
|
||||||
|
* Resets playback position to the start of the logfile
|
||||||
|
* through the emission of a replayCompleted signal.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool LogFile::resetReplay()
|
||||||
|
{
|
||||||
|
if (!m_file.isOpen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_timer.isActive()) {
|
||||||
|
m_timer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "LogFile - resetReplay";
|
||||||
|
m_replayState = STOPPED;
|
||||||
|
|
||||||
|
emit replayCompleted();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SLOT: resumeReplay()
|
||||||
|
*
|
||||||
|
* Resumes replay from the given position.
|
||||||
|
* If no position is given, resumes from the last position
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool LogFile::resumeReplay(quint32 desiredPosition)
|
||||||
|
{
|
||||||
|
if (m_timer.isActive()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
qDebug() << "LogFile - resumeReplay";
|
||||||
|
|
||||||
|
// Clear the playout buffer:
|
||||||
|
m_mutex.lock();
|
||||||
|
m_dataBuffer.clear();
|
||||||
|
m_mutex.unlock();
|
||||||
|
|
||||||
|
m_file.seek(0);
|
||||||
|
|
||||||
|
/* Skip through the logfile until we reach the desired position.
|
||||||
|
Looking for the next log timestamp after the desired position
|
||||||
|
has the advantage that it skips over parts of the log
|
||||||
|
where data might be missing.
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < m_timeStamps.size(); i++) {
|
||||||
|
if (m_timeStamps.at(i) >= desiredPosition) {
|
||||||
|
int bytesToSkip = m_timeStampPositions.at(i);
|
||||||
|
bool seek_ok = m_file.seek(bytesToSkip);
|
||||||
|
if (!seek_ok) {
|
||||||
|
qWarning() << "LogFile resumeReplay - an error occurred while seeking through the logfile.";
|
||||||
|
}
|
||||||
|
m_lastPlayed = m_timeStamps.at(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_file.read((char *)&m_nextTimeStamp, sizeof(m_nextTimeStamp));
|
||||||
|
|
||||||
|
// Real-time timestamps don't not need to match the log timestamps.
|
||||||
|
// However the delta between real-time variables "m_timeOffset" and "m_myTime" is important.
|
||||||
|
// This delta determines the number of log entries replayed per cycle.
|
||||||
|
|
||||||
|
// Set the real-time interval to 0 to start with:
|
||||||
|
m_myTime.restart();
|
||||||
|
m_timeOffset = 0;
|
||||||
|
|
||||||
|
m_replayState = PLAYING;
|
||||||
|
|
||||||
|
m_timer.start();
|
||||||
|
|
||||||
|
// Notify UI that playback has resumed
|
||||||
|
emit replayStarted();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SLOT: pauseReplay()
|
||||||
|
*
|
||||||
|
* Pauses replay while storing the current playback position
|
||||||
|
*
|
||||||
|
*/
|
||||||
bool LogFile::pauseReplay()
|
bool LogFile::pauseReplay()
|
||||||
{
|
{
|
||||||
if (!m_timer.isActive()) {
|
if (!m_timer.isActive()) {
|
||||||
@ -258,24 +413,149 @@ bool LogFile::pauseReplay()
|
|||||||
}
|
}
|
||||||
qDebug() << "LogFile - pauseReplay";
|
qDebug() << "LogFile - pauseReplay";
|
||||||
m_timer.stop();
|
m_timer.stop();
|
||||||
paused = true;
|
m_replayState = PAUSED;
|
||||||
|
|
||||||
// hack to notify UI that replay paused
|
|
||||||
emit replayStarted();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LogFile::resumeReplay()
|
/**
|
||||||
|
* SLOT: pauseReplayAndResetPosition()
|
||||||
|
*
|
||||||
|
* Pauses replay and resets the playback position to the start of the logfile
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool LogFile::pauseReplayAndResetPosition()
|
||||||
{
|
{
|
||||||
if (m_timer.isActive()) {
|
if (!m_file.isOpen() || !m_timer.isActive()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
qDebug() << "LogFile - resumeReplay";
|
qDebug() << "LogFile - pauseReplayAndResetPosition";
|
||||||
m_timeOffset = m_myTime.elapsed();
|
m_timer.stop();
|
||||||
m_timer.start();
|
m_replayState = STOPPED;
|
||||||
paused = false;
|
|
||||||
|
m_timeOffset = 0;
|
||||||
|
m_lastPlayed = m_timeStamps.at(0);
|
||||||
|
m_previousTimeStamp = 0;
|
||||||
|
m_nextTimeStamp = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FUNCTION: getReplayState()
|
||||||
|
*
|
||||||
|
* Returns the current replay status.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
ReplayState LogFile::getReplayState()
|
||||||
|
{
|
||||||
|
return m_replayState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FUNCTION: buildIndex()
|
||||||
|
*
|
||||||
|
* Walk through the opened logfile and stores the first and last position timestamps.
|
||||||
|
* Also builds an index for quickly skipping to a specific position in the logfile.
|
||||||
|
*
|
||||||
|
* Returns true when indexing has completed successfully.
|
||||||
|
* Returns false when a problem was encountered.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool LogFile::buildIndex()
|
||||||
|
{
|
||||||
|
quint32 timeStamp;
|
||||||
|
qint64 totalSize;
|
||||||
|
qint64 readPointer = 0;
|
||||||
|
quint64 index = 0;
|
||||||
|
int bytesRead = 0;
|
||||||
|
|
||||||
|
qDebug() << "LogFile - buildIndex";
|
||||||
|
|
||||||
|
// Ensure empty vectors:
|
||||||
|
m_timeStampPositions.clear();
|
||||||
|
m_timeStamps.clear();
|
||||||
|
|
||||||
|
QByteArray arr = m_file.readAll();
|
||||||
|
totalSize = arr.size();
|
||||||
|
QDataStream dataStream(&arr, QIODevice::ReadOnly);
|
||||||
|
|
||||||
|
// set the first timestamp
|
||||||
|
if (totalSize - readPointer >= TIMESTAMP_SIZE_BYTES) {
|
||||||
|
bytesRead = dataStream.readRawData((char *)&timeStamp, TIMESTAMP_SIZE_BYTES);
|
||||||
|
if (bytesRead != TIMESTAMP_SIZE_BYTES) {
|
||||||
|
qWarning() << "LogFile buildIndex - read first timeStamp: readRawData returned unexpected number of bytes:" << bytesRead << "at position" << readPointer << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_timeStamps.append(timeStamp);
|
||||||
|
m_timeStampPositions.append(readPointer);
|
||||||
|
readPointer += TIMESTAMP_SIZE_BYTES;
|
||||||
|
index++;
|
||||||
|
m_beginTimeStamp = timeStamp;
|
||||||
|
m_endTimeStamp = timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
qint64 dataSize;
|
||||||
|
|
||||||
|
// Check if there are enough bytes remaining for a correct "dataSize" field
|
||||||
|
if (totalSize - readPointer < (qint64)sizeof(dataSize)) {
|
||||||
|
qWarning() << "LogFile buildIndex - logfile corrupted! Unexpected end of file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the dataSize field and check for I/O errors
|
||||||
|
bytesRead = dataStream.readRawData((char *)&dataSize, sizeof(dataSize));
|
||||||
|
if (bytesRead != sizeof(dataSize)) {
|
||||||
|
qWarning() << "LogFile buildIndex - read dataSize: readRawData returned unexpected number of bytes:" << bytesRead << "at position" << readPointer << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
readPointer += sizeof(dataSize);
|
||||||
|
|
||||||
|
if (dataSize < 1 || dataSize > (1024 * 1024)) {
|
||||||
|
qWarning() << "LogFile buildIndex - logfile corrupted! Unlikely packet size: " << dataSize << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are enough bytes remaining
|
||||||
|
if (totalSize - readPointer < dataSize) {
|
||||||
|
qWarning() << "LogFile buildIndex - logfile corrupted! Unexpected end of file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip reading the data (we don't need it at this point)
|
||||||
|
readPointer += dataStream.skipRawData(dataSize);
|
||||||
|
|
||||||
|
// read the next timestamp
|
||||||
|
if (totalSize - readPointer >= TIMESTAMP_SIZE_BYTES) {
|
||||||
|
bytesRead = dataStream.readRawData((char *)&timeStamp, TIMESTAMP_SIZE_BYTES);
|
||||||
|
if (bytesRead != TIMESTAMP_SIZE_BYTES) {
|
||||||
|
qWarning() << "LogFile buildIndex - read timeStamp, readRawData returned unexpected number of bytes:" << bytesRead << "at position" << readPointer << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// some validity checks
|
||||||
|
if (timeStamp < m_endTimeStamp // logfile goes back in time
|
||||||
|
|| (timeStamp - m_endTimeStamp) > (60 * 60 * 1000)) { // gap of more than 60 minutes)
|
||||||
|
qWarning() << "LogFile buildIndex - logfile corrupted! Unlikely timestamp " << timeStamp << " after " << m_endTimeStamp;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_timeStamps.append(timeStamp);
|
||||||
|
m_timeStampPositions.append(readPointer);
|
||||||
|
readPointer += TIMESTAMP_SIZE_BYTES;
|
||||||
|
index++;
|
||||||
|
m_endTimeStamp = timeStamp;
|
||||||
|
} else {
|
||||||
|
// Break without error (we expect to end at this location when we are at the end of the logfile)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit timesChanged(m_beginTimeStamp, m_endTimeStamp);
|
||||||
|
|
||||||
|
// reset the read pointer to the start of the file
|
||||||
|
m_file.seek(0);
|
||||||
|
|
||||||
// hack to notify UI that replay resumed
|
|
||||||
emit replayStarted();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
******************************************************************************
|
******************************************************************************
|
||||||
*
|
*
|
||||||
* @file logfile.h
|
* @file logfile.h
|
||||||
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017.
|
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017-2018.
|
||||||
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||||
* @see The GNU Public License (GPL) Version 3
|
* @see The GNU Public License (GPL) Version 3
|
||||||
*
|
*
|
||||||
@ -32,8 +32,10 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QMutexLocker>
|
#include <QMutexLocker>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QBuffer>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
typedef enum { PLAYING, PAUSED, STOPPED } ReplayState;
|
||||||
|
|
||||||
class QTCREATOR_UTILS_EXPORT LogFile : public QIODevice {
|
class QTCREATOR_UTILS_EXPORT LogFile : public QIODevice {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -75,6 +77,8 @@ public:
|
|||||||
m_providedTimeStamp = providedTimestamp;
|
m_providedTimeStamp = providedTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReplayState getReplayState();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setReplaySpeed(double val)
|
void setReplaySpeed(double val)
|
||||||
{
|
{
|
||||||
@ -83,24 +87,28 @@ public slots:
|
|||||||
};
|
};
|
||||||
bool startReplay();
|
bool startReplay();
|
||||||
bool stopReplay();
|
bool stopReplay();
|
||||||
|
|
||||||
|
bool resumeReplay(quint32);
|
||||||
bool pauseReplay();
|
bool pauseReplay();
|
||||||
bool resumeReplay();
|
bool pauseReplayAndResetPosition();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void timerFired();
|
void timerFired();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void readReady();
|
|
||||||
void replayStarted();
|
void replayStarted();
|
||||||
void replayFinished();
|
void replayFinished(); // Emitted on error during replay or when logfile disconnected
|
||||||
|
void replayCompleted(); // Emitted at the end of normal logfile playback
|
||||||
|
void playbackPositionChanged(quint32);
|
||||||
|
void timesChanged(quint32, quint32);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QByteArray m_dataBuffer;
|
QByteArray m_dataBuffer;
|
||||||
QTimer m_timer;
|
QTimer m_timer;
|
||||||
QTime m_myTime;
|
QTime m_myTime;
|
||||||
QFile m_file;
|
QFile m_file;
|
||||||
qint32 m_previousTimeStamp;
|
quint32 m_previousTimeStamp;
|
||||||
qint32 m_nextTimeStamp;
|
quint32 m_nextTimeStamp;
|
||||||
double m_lastPlayed;
|
double m_lastPlayed;
|
||||||
// QMutex wants to be mutable
|
// QMutex wants to be mutable
|
||||||
// http://stackoverflow.com/questions/25521570/can-mutex-locking-function-be-marked-as-const
|
// http://stackoverflow.com/questions/25521570/can-mutex-locking-function-be-marked-as-const
|
||||||
@ -108,11 +116,19 @@ protected:
|
|||||||
|
|
||||||
int m_timeOffset;
|
int m_timeOffset;
|
||||||
double m_playbackSpeed;
|
double m_playbackSpeed;
|
||||||
bool paused;
|
ReplayState m_replayState;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_useProvidedTimeStamp;
|
bool m_useProvidedTimeStamp;
|
||||||
qint32 m_providedTimeStamp;
|
qint32 m_providedTimeStamp;
|
||||||
|
quint32 m_beginTimeStamp;
|
||||||
|
quint32 m_endTimeStamp;
|
||||||
|
quint32 m_timerTick;
|
||||||
|
QVector<quint32> m_timeStamps;
|
||||||
|
QVector<qint64> m_timeStampPositions;
|
||||||
|
|
||||||
|
bool buildIndex();
|
||||||
|
bool resetReplay();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LOGFILE_H
|
#endif // LOGFILE_H
|
||||||
|
BIN
ground/gcs/src/plugins/logging/images/pause.png
Normal file
BIN
ground/gcs/src/plugins/logging/images/pause.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
ground/gcs/src/plugins/logging/images/play.png
Normal file
BIN
ground/gcs/src/plugins/logging/images/play.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
ground/gcs/src/plugins/logging/images/stop.png
Normal file
BIN
ground/gcs/src/plugins/logging/images/stop.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 970 B |
@ -24,3 +24,5 @@ OTHER_FILES += LoggingGadget.pluginspec
|
|||||||
|
|
||||||
FORMS += logging.ui
|
FORMS += logging.ui
|
||||||
|
|
||||||
|
RESOURCES += \
|
||||||
|
res.qrc
|
||||||
|
@ -7,75 +7,156 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>439</width>
|
<width>439</width>
|
||||||
<height>122</height>
|
<height>120</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
<horstretch>100</horstretch>
|
<horstretch>1</horstretch>
|
||||||
<verstretch>80</verstretch>
|
<verstretch>1</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>100</width>
|
<width>100</width>
|
||||||
<height>80</height>
|
<height>118</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0">
|
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,2,0,0,2,0,0">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
<property name="sizeConstraint">
|
<property name="sizeConstraint">
|
||||||
<enum>QLayout::SetNoConstraint</enum>
|
<enum>QLayout::SetNoConstraint</enum>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="playButton">
|
<widget class="QPushButton" name="playButton">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>30</width>
|
<width>39</width>
|
||||||
<height>0</height>
|
<height>30</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="maximumSize">
|
||||||
<string>Play</string>
|
<size>
|
||||||
|
<width>39</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::NoFocus</enum>
|
||||||
|
</property>
|
||||||
|
<property name="autoFillBackground">
|
||||||
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="../notify/res.qrc">
|
<iconset resource="res.qrc">
|
||||||
<normaloff>:/notify/images/play.png</normaloff>:/notify/images/play.png</iconset>
|
<normaloff>:/logging/images/play.png</normaloff>:/logging/images/play.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>16</width>
|
||||||
|
<height>16</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="pauseButton">
|
<widget class="QPushButton" name="pauseButton">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>30</width>
|
<width>39</width>
|
||||||
<height>0</height>
|
<height>30</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="maximumSize">
|
||||||
<string notr="true">Pause</string>
|
<size>
|
||||||
|
<width>39</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="../notify/res.qrc">
|
<iconset resource="res.qrc">
|
||||||
<normaloff>:/notify/images/stop.png</normaloff>:/notify/images/stop.png</iconset>
|
<normaloff>:/logging/images/pause.png</normaloff>:/logging/images/pause.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>16</width>
|
||||||
|
<height>16</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_5">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>4</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="stopButton">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>39</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>39</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="res.qrc">
|
||||||
|
<normaloff>:/logging/images/stop.png</normaloff>:/logging/images/stop.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>16</width>
|
||||||
|
<height>16</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -92,6 +173,54 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Playback speed:</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDoubleSpinBox" name="playbackSpeed">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="decimals">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.100000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>10.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.100000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_4">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -101,6 +230,12 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="statusLabel">
|
<widget class="QLabel" name="statusLabel">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>65</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<weight>75</weight>
|
<weight>75</weight>
|
||||||
@ -115,29 +250,50 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<widget class="QSlider" name="playbackPosition">
|
||||||
|
<property name="tracking">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="startTimeLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Playback speed:</string>
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QDoubleSpinBox" name="playbackSpeed">
|
<spacer name="horizontalSpacer_3">
|
||||||
<property name="maximum">
|
|
||||||
<double>10.000000000000000</double>
|
|
||||||
</property>
|
|
||||||
<property name="singleStep">
|
|
||||||
<double>0.100000000000000</double>
|
|
||||||
</property>
|
|
||||||
<property name="value">
|
|
||||||
<double>1.000000000000000</double>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
@ -149,6 +305,63 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="positionTimestampLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="endTimeLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -161,7 +374,7 @@
|
|||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
<width>20</width>
|
<width>20</width>
|
||||||
<height>40</height>
|
<height>5</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
@ -169,7 +382,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../notify/res.qrc"/>
|
<include location="res.qrc"/>
|
||||||
</resources>
|
</resources>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*
|
*
|
||||||
* @file GCSControlgadgetwidget.cpp
|
* @file logginggadgetwidget.cpp
|
||||||
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2018.
|
||||||
|
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||||
* @addtogroup GCSPlugins GCS Plugins
|
* @addtogroup GCSPlugins GCS Plugins
|
||||||
* @{
|
* @{
|
||||||
* @addtogroup GCSControlGadgetPlugin GCSControl Gadget Plugin
|
* @addtogroup LoggingGadgetPlugin Logging Gadget Plugin
|
||||||
* @{
|
* @{
|
||||||
* @brief A gadget to control the UAV, either from the keyboard or a joystick
|
* @brief A gadget to control playback of a GCS log.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
/*
|
/*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@ -40,6 +41,14 @@ LoggingGadgetWidget::LoggingGadgetWidget(QWidget *parent) : QWidget(parent), log
|
|||||||
|
|
||||||
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
|
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
|
||||||
scpPlugin = pm->getObject<ScopeGadgetFactory>();
|
scpPlugin = pm->getObject<ScopeGadgetFactory>();
|
||||||
|
|
||||||
|
disableWidgets();
|
||||||
|
|
||||||
|
// Configure timer to delay application of slider position action for 200ms
|
||||||
|
sliderActionDelay.setSingleShot(true);
|
||||||
|
sliderActionDelay.setInterval(200);
|
||||||
|
|
||||||
|
connect(&sliderActionDelay, SIGNAL(timeout()), this, SLOT(sliderAction()));
|
||||||
}
|
}
|
||||||
|
|
||||||
LoggingGadgetWidget::~LoggingGadgetWidget()
|
LoggingGadgetWidget::~LoggingGadgetWidget()
|
||||||
@ -50,20 +59,82 @@ LoggingGadgetWidget::~LoggingGadgetWidget()
|
|||||||
void LoggingGadgetWidget::setPlugin(LoggingPlugin *p)
|
void LoggingGadgetWidget::setPlugin(LoggingPlugin *p)
|
||||||
{
|
{
|
||||||
loggingPlugin = p;
|
loggingPlugin = p;
|
||||||
|
|
||||||
connect(m_logging->playButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::startPlotting);
|
|
||||||
connect(m_logging->pauseButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::stopPlotting);
|
|
||||||
|
|
||||||
LogFile *logFile = loggingPlugin->getLogfile();
|
LogFile *logFile = loggingPlugin->getLogfile();
|
||||||
connect(m_logging->playButton, &QPushButton::clicked, logFile, &LogFile::resumeReplay);
|
|
||||||
connect(m_logging->pauseButton, &QPushButton::clicked, logFile, &LogFile::pauseReplay);
|
// GUI elements to gadgetwidget functions
|
||||||
|
connect(m_logging->playButton, &QPushButton::clicked, this, &LoggingGadgetWidget::playButtonAction);
|
||||||
|
connect(m_logging->pauseButton, &QPushButton::clicked, this, &LoggingGadgetWidget::pauseButtonAction);
|
||||||
|
connect(m_logging->stopButton, &QPushButton::clicked, this, &LoggingGadgetWidget::stopButtonAction);
|
||||||
|
connect(m_logging->playbackPosition, &QSlider::valueChanged, this, &LoggingGadgetWidget::sliderMoved);
|
||||||
|
|
||||||
connect(m_logging->playbackSpeed, static_cast<void(QDoubleSpinBox::*) (double)>(&QDoubleSpinBox::valueChanged), logFile, &LogFile::setReplaySpeed);
|
connect(m_logging->playbackSpeed, static_cast<void(QDoubleSpinBox::*) (double)>(&QDoubleSpinBox::valueChanged), logFile, &LogFile::setReplaySpeed);
|
||||||
|
|
||||||
connect(loggingPlugin, &LoggingPlugin::stateChanged, this, &LoggingGadgetWidget::stateChanged);
|
// gadgetwidget functions to logfile actions
|
||||||
|
connect(this, &LoggingGadgetWidget::resumeReplay, logFile, &LogFile::resumeReplay);
|
||||||
|
connect(this, &LoggingGadgetWidget::pauseReplay, logFile, &LogFile::pauseReplay);
|
||||||
|
connect(this, &LoggingGadgetWidget::pauseReplayAndResetPosition, logFile, &LogFile::pauseReplayAndResetPosition);
|
||||||
|
|
||||||
|
// gadgetwidget functions to scope actions
|
||||||
|
connect(this, &LoggingGadgetWidget::resumeReplay, scpPlugin, &ScopeGadgetFactory::startPlotting);
|
||||||
|
connect(this, &LoggingGadgetWidget::pauseReplay, scpPlugin, &ScopeGadgetFactory::stopPlotting);
|
||||||
|
connect(this, &LoggingGadgetWidget::pauseReplayAndResetPosition, scpPlugin, &ScopeGadgetFactory::stopPlotting);
|
||||||
|
|
||||||
|
// Feedback from logfile to GUI
|
||||||
|
connect(loggingPlugin, &LoggingPlugin::stateChanged, this, &LoggingGadgetWidget::stateChanged);
|
||||||
|
connect(logFile, &LogFile::timesChanged, this, &LoggingGadgetWidget::setBeginAndEndTimes);
|
||||||
|
connect(logFile, &LogFile::playbackPositionChanged, this, &LoggingGadgetWidget::setPlaybackPosition);
|
||||||
|
connect(logFile, &LogFile::replayStarted, this, &LoggingGadgetWidget::enableWidgets);
|
||||||
|
connect(logFile, &LogFile::replayFinished, this, &LoggingGadgetWidget::disableWidgets);
|
||||||
|
connect(logFile, &LogFile::replayCompleted, this, &LoggingGadgetWidget::stopButtonAction);
|
||||||
|
|
||||||
|
// Feedback from logfile to scope
|
||||||
|
connect(logFile, &LogFile::replayFinished, scpPlugin, &ScopeGadgetFactory::stopPlotting);
|
||||||
|
|
||||||
|
// Perform actions as if the plugin state has been changed
|
||||||
stateChanged(loggingPlugin->getState());
|
stateChanged(loggingPlugin->getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::playButtonAction()
|
||||||
|
{
|
||||||
|
ReplayState replayState = loggingPlugin->getLogfile()->getReplayState();
|
||||||
|
|
||||||
|
if (replayState != PLAYING) {
|
||||||
|
emit resumeReplay(m_logging->playbackPosition->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_logging->playButton->setVisible(false);
|
||||||
|
m_logging->pauseButton->setVisible(true);
|
||||||
|
m_logging->stopButton->setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::pauseButtonAction()
|
||||||
|
{
|
||||||
|
ReplayState replayState = loggingPlugin->getLogfile()->getReplayState();
|
||||||
|
|
||||||
|
if (replayState == PLAYING) {
|
||||||
|
emit pauseReplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_logging->playButton->setVisible(true);
|
||||||
|
m_logging->pauseButton->setVisible(false);
|
||||||
|
m_logging->stopButton->setEnabled(true);
|
||||||
|
|
||||||
|
m_logging->statusLabel->setText(tr("Paused"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::stopButtonAction()
|
||||||
|
{
|
||||||
|
emit pauseReplayAndResetPosition();
|
||||||
|
|
||||||
|
m_logging->playButton->setVisible(true);
|
||||||
|
m_logging->pauseButton->setVisible(false);
|
||||||
|
m_logging->stopButton->setEnabled(false);
|
||||||
|
|
||||||
|
setPlaybackPosition(0);
|
||||||
|
|
||||||
|
m_logging->statusLabel->setText(tr("Stopped"));
|
||||||
|
}
|
||||||
|
|
||||||
void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state)
|
void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state)
|
||||||
{
|
{
|
||||||
QString status;
|
QString status;
|
||||||
@ -71,7 +142,8 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state)
|
|||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case LoggingPlugin::IDLE:
|
case LoggingPlugin::IDLE:
|
||||||
status = tr("Idle");
|
status = tr("Idle");
|
||||||
|
setPlaybackPosition(0);
|
||||||
break;
|
break;
|
||||||
case LoggingPlugin::LOGGING:
|
case LoggingPlugin::LOGGING:
|
||||||
status = tr("Logging");
|
status = tr("Logging");
|
||||||
@ -84,10 +156,126 @@ void LoggingGadgetWidget::stateChanged(LoggingPlugin::State state)
|
|||||||
m_logging->statusLabel->setText(status);
|
m_logging->statusLabel->setText(status);
|
||||||
|
|
||||||
bool playing = loggingPlugin->getLogfile()->isPlaying();
|
bool playing = loggingPlugin->getLogfile()->isPlaying();
|
||||||
m_logging->playButton->setEnabled(enabled && !playing);
|
m_logging->playButton->setEnabled(enabled);
|
||||||
m_logging->pauseButton->setEnabled(enabled && playing);
|
m_logging->pauseButton->setEnabled(enabled);
|
||||||
|
m_logging->stopButton->setEnabled(enabled && playing);
|
||||||
|
|
||||||
|
if (playing) {
|
||||||
|
m_logging->playButton->setVisible(false);
|
||||||
|
m_logging->pauseButton->setVisible(true);
|
||||||
|
} else {
|
||||||
|
m_logging->playButton->setVisible(true);
|
||||||
|
m_logging->pauseButton->setVisible(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::setBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp)
|
||||||
|
{
|
||||||
|
int startSec, startMin, endSec, endMin;
|
||||||
|
|
||||||
|
startSec = (startTimeStamp / 1000) % 60;
|
||||||
|
startMin = startTimeStamp / (60 * 1000);
|
||||||
|
|
||||||
|
endSec = (endTimeStamp / 1000) % 60;
|
||||||
|
endMin = endTimeStamp / (60 * 1000);
|
||||||
|
|
||||||
|
// update start and end labels
|
||||||
|
m_logging->startTimeLabel->setText(QString("%1:%2").arg(startMin, 2, 10, QChar('0')).arg(startSec, 2, 10, QChar('0')));
|
||||||
|
m_logging->endTimeLabel->setText(QString("%1:%2").arg(endMin, 2, 10, QChar('0')).arg(endSec, 2, 10, QChar('0')));
|
||||||
|
|
||||||
|
// Update position bar
|
||||||
|
m_logging->playbackPosition->setMinimum(startTimeStamp);
|
||||||
|
m_logging->playbackPosition->setMaximum(endTimeStamp);
|
||||||
|
|
||||||
|
m_logging->playbackPosition->setSingleStep((endTimeStamp - startTimeStamp) / 100);
|
||||||
|
m_logging->playbackPosition->setPageStep((endTimeStamp - startTimeStamp) / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::setPlaybackPosition(quint32 positionTimeStamp)
|
||||||
|
{
|
||||||
|
// Update position bar, but only if the user is not updating the slider position
|
||||||
|
if (!m_logging->playbackPosition->isSliderDown() && !sliderActionDelay.isActive()) {
|
||||||
|
// Block signals during slider position update:
|
||||||
|
m_logging->playbackPosition->blockSignals(true);
|
||||||
|
m_logging->playbackPosition->setValue(positionTimeStamp);
|
||||||
|
m_logging->playbackPosition->blockSignals(false);
|
||||||
|
|
||||||
|
// update position label
|
||||||
|
updatePositionLabel(positionTimeStamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::enableWidgets()
|
||||||
|
{
|
||||||
|
ReplayState replayState = loggingPlugin->getLogfile()->getReplayState();
|
||||||
|
|
||||||
|
m_logging->playButton->setEnabled(true);
|
||||||
|
m_logging->pauseButton->setEnabled(true);
|
||||||
|
|
||||||
|
switch (replayState) {
|
||||||
|
case STOPPED:
|
||||||
|
m_logging->stopButton->setEnabled(false);
|
||||||
|
m_logging->playButton->setVisible(true);
|
||||||
|
m_logging->pauseButton->setVisible(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PLAYING:
|
||||||
|
m_logging->stopButton->setEnabled(true);
|
||||||
|
m_logging->playButton->setVisible(false);
|
||||||
|
m_logging->pauseButton->setVisible(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PAUSED:
|
||||||
|
m_logging->stopButton->setEnabled(true);
|
||||||
|
m_logging->playButton->setVisible(true);
|
||||||
|
m_logging->pauseButton->setVisible(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_logging->playbackPosition->setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::disableWidgets()
|
||||||
|
{
|
||||||
|
m_logging->playButton->setVisible(true);
|
||||||
|
m_logging->pauseButton->setVisible(false);
|
||||||
|
|
||||||
|
m_logging->playButton->setEnabled(false);
|
||||||
|
m_logging->pauseButton->setEnabled(false);
|
||||||
|
m_logging->stopButton->setEnabled(false);
|
||||||
|
|
||||||
|
m_logging->playbackPosition->setEnabled(false);
|
||||||
|
|
||||||
|
// reset start and end labels
|
||||||
|
m_logging->startTimeLabel->setText("");
|
||||||
|
m_logging->endTimeLabel->setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::updatePositionLabel(quint32 positionTimeStamp)
|
||||||
|
{
|
||||||
|
// update position label -> MM:SS
|
||||||
|
int sec = (positionTimeStamp / 1000) % 60;
|
||||||
|
int min = positionTimeStamp / (60 * 1000);
|
||||||
|
|
||||||
|
m_logging->positionTimestampLabel->setText(QString("%1:%2").arg(min, 2, 10, QChar('0')).arg(sec, 2, 10, QChar('0')));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::sliderMoved(int position)
|
||||||
|
{
|
||||||
|
// pause playback while the user is dragging the slider to change position
|
||||||
|
emit pauseReplay();
|
||||||
|
|
||||||
|
updatePositionLabel(position);
|
||||||
|
|
||||||
|
// Start or restarts a time-out after which replay will resume from the new position.
|
||||||
|
sliderActionDelay.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingGadgetWidget::sliderAction()
|
||||||
|
{
|
||||||
|
emit resumeReplay(m_logging->playbackPosition->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
* @}
|
* @}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*
|
*
|
||||||
* @file GCSControlgadgetwidget.h
|
* @file logginggadgetwidget.h
|
||||||
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2018.
|
||||||
|
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
|
||||||
* @addtogroup GCSPlugins GCS Plugins
|
* @addtogroup GCSPlugins GCS Plugins
|
||||||
* @{
|
* @{
|
||||||
* @addtogroup GCSControlGadgetPlugin GCSControl Gadget Plugin
|
* @addtogroup LoggingGadgetPlugin Logging Gadget Plugin
|
||||||
* @{
|
* @{
|
||||||
* @brief A place holder gadget plugin
|
* @brief A gadget to control playback of a GCS log.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
/*
|
/*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@ -37,6 +38,7 @@
|
|||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
class Ui_Logging;
|
class Ui_Logging;
|
||||||
|
class QTimer;
|
||||||
|
|
||||||
class LoggingGadgetWidget : public QWidget {
|
class LoggingGadgetWidget : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -48,15 +50,28 @@ public:
|
|||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void stateChanged(LoggingPlugin::State state);
|
void stateChanged(LoggingPlugin::State state);
|
||||||
|
void setBeginAndEndTimes(quint32 startTimeStamp, quint32 endTimeStamp);
|
||||||
|
void setPlaybackPosition(quint32 positionTimeStamp);
|
||||||
|
void playButtonAction();
|
||||||
|
void pauseButtonAction();
|
||||||
|
void stopButtonAction();
|
||||||
|
void enableWidgets();
|
||||||
|
void disableWidgets();
|
||||||
|
void sliderMoved(int);
|
||||||
|
void sliderAction();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void pause();
|
void resumeReplay(quint32 positionTimeStamp);
|
||||||
void play();
|
void pauseReplay();
|
||||||
|
void pauseReplayAndResetPosition();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui_Logging *m_logging;
|
Ui_Logging *m_logging;
|
||||||
LoggingPlugin *loggingPlugin;
|
LoggingPlugin *loggingPlugin;
|
||||||
ScopeGadgetFactory *scpPlugin;
|
ScopeGadgetFactory *scpPlugin;
|
||||||
|
QTimer sliderActionDelay;
|
||||||
|
|
||||||
|
void updatePositionLabel(quint32 positionTimeStamp);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* LoggingGADGETWIDGET_H_ */
|
#endif /* LoggingGADGETWIDGET_H_ */
|
||||||
|
7
ground/gcs/src/plugins/logging/res.qrc
Normal file
7
ground/gcs/src/plugins/logging/res.qrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/logging">
|
||||||
|
<file>images/play.png</file>
|
||||||
|
<file>images/pause.png</file>
|
||||||
|
<file>images/stop.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
Loading…
x
Reference in New Issue
Block a user