8#include <QtCore/QFileInfo>
9#include <QtCore/QtEndian>
10#include <QtCore/QThread>
11#include <QtCore/QTimer>
20 qCDebug(LogReplayLinkLog) <<
this;
25 , _logFilename(copy->logFilename())
27 qCDebug(LogReplayLinkLog) <<
this;
32 qCDebug(LogReplayLinkLog) <<
this;
38 if (!logReplaySource) {
39 qCWarning(LogReplayLinkLog) <<
"Invalid source configuration type";
50 settings.beginGroup(root);
59 settings.beginGroup(root);
61 settings.setValue(
"logFilename", _logFilename);
68 return QFileInfo(_logFilename).fileName();
85 qCDebug(LogReplayLinkLog) <<
this;
92 qCDebug(LogReplayLinkLog) <<
this;
97 if (!_readTickTimer) {
98 _readTickTimer =
new QTimer(
this);
101 (void) connect(_readTickTimer, &QTimer::timeout,
this, &LogReplayWorker::_readNextLogEntry);
107 qCWarning(LogReplayLinkLog) <<
"Already connected";
112 emit
errorOccurred(tr(
"You must close all connections prior to replaying a log."));
116 if (!_loadLogFile()) {
130 qCDebug(LogReplayLinkLog) <<
"Already disconnected";
134 qCDebug(LogReplayLinkLog) <<
"Disconnecting from log";
136 if (_readTickTimer) {
137 _readTickTimer->stop();
140 if (_logFile.isOpen()) {
144 _isConnected =
false;
150 return (_readTickTimer && _readTickTimer->isActive());
158 if (_logFile.atEnd()) {
159 _resetPlaybackToBeginning();
162 _playbackStartTimeMSecs =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch());
163 _playbackStartLogTimeUSecs = _logCurrentTimeUSecs;
164 _readTickTimer->start(1);
174 _readTickTimer->stop();
181 _playbackSpeed = playbackSpeed;
182 _playbackStartTimeMSecs =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch());
183 _playbackStartLogTimeUSecs = _logCurrentTimeUSecs;
184 _readTickTimer->start(1);
191 if (_readTickTimer->isActive()) {
196 percentComplete = qBound(0., percentComplete, 100.);
197 const qreal percentCompleteMult = percentComplete / 100.0;
198 const qint64 newFilePos =
static_cast<qint64
>(percentCompleteMult *
static_cast<qreal
>(_logFile.size()));
199 if (!_logFile.seek(newFilePos)) {
205 _logCurrentTimeUSecs = _seekToNextMavlinkMessage(dummy);
207 qreal newRelativeTimeUSecs =
static_cast<qreal
>(_logCurrentTimeUSecs - _logStartTimeUSecs);
208 const qreal baudRate = _logFile.size() /
static_cast<qreal
>(_logDurationUSecs) / 1e6;
209 const qreal desiredTimeUSecs = percentCompleteMult * _logDurationUSecs;
210 const qint64 offset = (newRelativeTimeUSecs - desiredTimeUSecs) * baudRate;
211 if (!_logFile.seek(_logFile.pos() + offset)) {
216 _logCurrentTimeUSecs = _seekToNextMavlinkMessage(dummy);
217 _signalCurrentLogTimeSecs();
219 newRelativeTimeUSecs =
static_cast<qreal
>(_logCurrentTimeUSecs - _logStartTimeUSecs);
220 percentComplete = ((newRelativeTimeUSecs / _logDurationUSecs) * 100);
224void LogReplayWorker::_resetPlaybackToBeginning()
226 if (_logFile.isOpen()) {
227 if (!_logFile.reset()) {
228 qCWarning(LogReplayLinkLog) <<
"failed to reset log file:" << _logFile.error() << _logFile.errorString();
232 _playbackStartTimeMSecs = 0;
233 _playbackStartLogTimeUSecs = 0;
234 _logCurrentTimeUSecs = _logStartTimeUSecs;
237void LogReplayWorker::_readNextLogEntry()
239 int timeToNextExecutionMSecs = 0;
240 while (timeToNextExecutionMSecs < 3) {
242 bytes.reserve(_logFile.bytesAvailable());
243 const qint64 nextTimeUSecs = _readNextMavlinkMessage(bytes);
245 emit
playbackPercentCompleteChanged((
static_cast<float>(_logCurrentTimeUSecs - _logStartTimeUSecs) /
static_cast<float>(_logDurationUSecs)) * 100);
247 if (_logFile.atEnd()) {
253 _logCurrentTimeUSecs = nextTimeUSecs;
255 const quint64 currentTimeMSecs =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch());
256 const quint64 desiredPlayheadMovementTimeMSecs = ((_logCurrentTimeUSecs - _playbackStartLogTimeUSecs) / 1000) / _playbackSpeed;
257 const quint64 desiredCurrentTimeMSecs = _playbackStartTimeMSecs + desiredPlayheadMovementTimeMSecs;
258 timeToNextExecutionMSecs = desiredCurrentTimeMSecs - currentTimeMSecs;
261 _signalCurrentLogTimeSecs();
263 _readTickTimer->start(timeToNextExecutionMSecs);
266void LogReplayWorker::_signalCurrentLogTimeSecs()
271bool LogReplayWorker::_loadLogFile()
273 if (_logFile.isOpen()) {
275 emit
errorOccurred(tr(
"Attempt to load new log while log being played"));
279 const QString logFilename = _logReplayConfig->
logFilename();
280 _logFile.setFileName(logFilename);
281 if (!_logFile.open(QFile::ReadOnly)) {
282 emit
errorOccurred(tr(
"Unable to open log file: '%1', error: %2").arg(logFilename, _logFile.errorString()));
286 QFileInfo logFileInfo;
287 logFileInfo.setFile(logFilename);
288 _logFileSize = logFileInfo.size();
290 const quint64 startTimeUSecs = _parseTimestamp(_logFile.read(kTimestamp));
291 const quint64 endTimeUSecs = _findLastTimestamp();
292 if (endTimeUSecs <= startTimeUSecs) {
294 emit
errorOccurred(tr(
"The log file '%1' is corrupt or empty.").arg(logFilename));
298 _logEndTimeUSecs = endTimeUSecs;
299 _logStartTimeUSecs = startTimeUSecs;
300 _logDurationUSecs = endTimeUSecs - startTimeUSecs;
301 _logCurrentTimeUSecs = startTimeUSecs;
303 if (!_logFile.reset()) {
304 qCWarning(LogReplayLinkLog) <<
"failed to reset log file:" << _logFile.error() << _logFile.errorString();
307 const quint64 logDurationSecondsTotal = _logDurationUSecs / 1000000;
313quint64 LogReplayWorker::_parseTimestamp(
const QByteArray &bytes)
316 if (bytes.size() <
static_cast<qsizetype
>(
sizeof(quint64))) {
320 const quint64 currentTimestamp =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch()) * 1000;
323 quint64 timestamp = qFromBigEndian<quint64>(bytes.constData());
324 if (timestamp > currentTimestamp) {
325 timestamp = qbswap(timestamp);
331quint64 LogReplayWorker::_readNextMavlinkMessage(QByteArray &bytes)
336 while (_logFile.getChar(&nextByte)) {
338 mavlink_status_t status{};
339 const bool messageFound = mavlink_parse_char(_mavlinkChannel, nextByte, &message, &status);
341 if (status.parse_state == MAVLINK_PARSE_STATE_GOT_STX) {
344 (void) bytes.append(nextByte);
347 const QByteArray rawTime = _logFile.read(kTimestamp);
348 return _parseTimestamp(rawTime);
357 mavlink_reset_channel_status(_mavlinkChannel);
359 qint64 messageStartPos = -1;
361 while (_logFile.getChar(&nextByte)) {
362 mavlink_status_t status{};
363 const bool messageFound = mavlink_parse_char(_mavlinkChannel, nextByte, &nextMsg, &status);
365 if (status.parse_state == MAVLINK_PARSE_STATE_GOT_STX) {
366 messageStartPos = _logFile.pos() - 1;
369 if (messageFound && (messageStartPos != -1)) {
370 if (!_logFile.seek(messageStartPos - kTimestamp)) {
371 qCWarning(LogReplayLinkLog) <<
"Failed to seek next message:" << _logFile.error() << _logFile.errorString();
375 const QByteArray rawTime = _logFile.read(kTimestamp);
376 return _parseTimestamp(rawTime);
383quint64 LogReplayWorker::_findLastTimestamp()
385 if (!_logFile.reset()) {
386 qCWarning(LogReplayLinkLog) <<
"failed to reset log file:" << _logFile.error() << _logFile.errorString();
389 mavlink_reset_channel_status(_mavlinkChannel);
391 quint64 lastTimestamp = 0;
393 while (_logFile.bytesAvailable() >=
static_cast<qint64
>(kTimestamp)) {
394 const quint64 candidateTimestamp = _parseTimestamp(_logFile.read(kTimestamp));
396 bool endOfMessage =
false;
398 while (!endOfMessage && _logFile.getChar(&nextByte)) {
400 mavlink_status_t status{};
401 endOfMessage = mavlink_parse_char(_mavlinkChannel, nextByte, &msg, &status);
405 if (lastTimestamp != 0) {
409 qCWarning(LogReplayLinkLog)
410 <<
"Ignoring trailing bytes at end of log file which do not form a complete MAVLink message";
412 qCWarning(LogReplayLinkLog) <<
"No complete MAVLink message found in log file";
417 lastTimestamp = candidateTimestamp;
420 return lastTimestamp;
429 , _workerThread(new QThread(this))
431 qCDebug(LogReplayLinkLog) <<
this;
433 _workerThread->setObjectName(QStringLiteral(
"LogReplay_%1").arg(_logReplayConfig->
name()));
435 _worker->moveToThread(_workerThread);
438 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
452 _workerThread->start();
461 (void) QMetaObject::invokeMethod(_worker,
"disconnectFromLog", Qt::QueuedConnection);
467 qCDebug(LogReplayLinkLog) <<
this;
475bool LogReplayLink::_connect()
477 return QMetaObject::invokeMethod(_worker,
"connectToLog", Qt::QueuedConnection);
483 (void) QMetaObject::invokeMethod(_worker,
"disconnectFromLog", Qt::QueuedConnection);
487void LogReplayLink::_onConnected()
489 _disconnectedEmitted =
false;
493void LogReplayLink::_onDisconnected()
495 if (!_disconnectedEmitted.exchange(
true)) {
500void LogReplayLink::_onErrorOccurred(
const QString &
errorString)
502 qCWarning(LogReplayLinkLog) <<
"Error:" <<
errorString;
506void LogReplayLink::_onDataReceived(
const QByteArray &data)
518 (void) QMetaObject::invokeMethod(_worker,
"play", Qt::QueuedConnection);
523 (void) QMetaObject::invokeMethod(_worker,
"pause", Qt::QueuedConnection);
528 (void) QMetaObject::invokeMethod(_worker,
"setPlaybackSpeed", Qt::QueuedConnection, playbackSpeed);
533 (void) QMetaObject::invokeMethod(_worker,
"movePlayhead", Qt::QueuedConnection, percentComplete);
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Interface holding link specific settings.
virtual void copyFrom(const LinkConfiguration *source)
The link interface defines the interface for all links used to communicate with the ground station ap...
void bytesReceived(LinkInterface *link, const QByteArray &data)
void _shutdownWorkerThread(QThread *thread, const QLoggingCategory &category, bool allowTerminate=true)
void communicationError(const QString &title, const QString &error)
void setConnectionsAllowed()
Sets the flag to allow new connections to be made.
static LinkManager * instance()
void setConnectionsSuspended(const QString &reason)
QString logFilenameShort() const
LogReplayConfiguration(const QString &name, QObject *parent=nullptr)
void copyFrom(const LinkConfiguration *source) override
void saveSettings(QSettings &settings, const QString &root) const override
void loadSettings(QSettings &settings, const QString &root) override
virtual ~LogReplayConfiguration()
QString logFilename() const
void setLogFilename(const QString &logFilename)
void disconnect() override
bool isConnected() const override
void playbackPercentCompleteChanged(qreal percentComplete)
void logFileStats(uint32_t logDurationSecs)
void currentLogTimeSecs(uint32_t secs)
void setPlaybackSpeed(qreal playbackSpeed)
void movePlayhead(qreal percentComplete)
LogReplayLink(SharedLinkConfigurationPtr &config, QObject *parent=nullptr)
void currentLogTimeSecs(uint32_t secs)
void dataReceived(const QByteArray &data)
void setPlaybackSpeed(qreal playbackSpeed)
void logFileStats(uint32_t logDurationSecs)
void errorOccurred(const QString &errorString)
void movePlayhead(qreal percentComplete)
LogReplayWorker(const LogReplayConfiguration *config, QObject *parent=nullptr)
void playbackPercentCompleteChanged(qreal percentComplete)
static MAVLinkProtocol * instance()
void suspendLogForReplay(bool suspend)
static MultiVehicleManager * instance()