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;
46 settings.beginGroup(root);
55 settings.beginGroup(root);
57 settings.setValue(
"logFilename", _logFilename);
64 return QFileInfo(_logFilename).fileName();
79 , _logReplayConfig(config)
81 qCDebug(LogReplayLinkLog) <<
this;
88 qCDebug(LogReplayLinkLog) <<
this;
93 if (!_readTickTimer) {
94 _readTickTimer =
new QTimer(
this);
97 (void) connect(_readTickTimer, &QTimer::timeout,
this, &LogReplayWorker::_readNextLogEntry);
103 qCWarning(LogReplayLinkLog) <<
"Already connected";
108 emit
errorOccurred(tr(
"You must close all connections prior to replaying a log."));
112 if (!_loadLogFile()) {
126 qCDebug(LogReplayLinkLog) <<
"Already disconnected";
130 qCDebug(LogReplayLinkLog) <<
"Disconnecting from log";
132 if (_readTickTimer) {
133 _readTickTimer->stop();
136 if (_logFile.isOpen()) {
140 _isConnected =
false;
146 return (_readTickTimer && _readTickTimer->isActive());
154 if (_logFile.atEnd()) {
155 _resetPlaybackToBeginning();
158 _playbackStartTimeMSecs =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch());
159 _playbackStartLogTimeUSecs = _logCurrentTimeUSecs;
160 _readTickTimer->start(1);
170 _readTickTimer->stop();
177 _playbackSpeed = playbackSpeed;
178 _playbackStartTimeMSecs =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch());
179 _playbackStartLogTimeUSecs = _logCurrentTimeUSecs;
180 _readTickTimer->start(1);
187 if (_readTickTimer->isActive()) {
192 percentComplete = qBound(0., percentComplete, 100.);
193 const qreal percentCompleteMult = percentComplete / 100.0;
194 const qint64 newFilePos =
static_cast<qint64
>(percentCompleteMult *
static_cast<qreal
>(_logFile.size()));
195 if (!_logFile.seek(newFilePos)) {
201 _logCurrentTimeUSecs = _seekToNextMavlinkMessage(dummy);
203 qreal newRelativeTimeUSecs =
static_cast<qreal
>(_logCurrentTimeUSecs - _logStartTimeUSecs);
204 const qreal baudRate = _logFile.size() /
static_cast<qreal
>(_logDurationUSecs) / 1e6;
205 const qreal desiredTimeUSecs = percentCompleteMult * _logDurationUSecs;
206 const qint64 offset = (newRelativeTimeUSecs - desiredTimeUSecs) * baudRate;
207 if (!_logFile.seek(_logFile.pos() + offset)) {
212 _logCurrentTimeUSecs = _seekToNextMavlinkMessage(dummy);
213 _signalCurrentLogTimeSecs();
215 newRelativeTimeUSecs =
static_cast<qreal
>(_logCurrentTimeUSecs - _logStartTimeUSecs);
216 percentComplete = ((newRelativeTimeUSecs / _logDurationUSecs) * 100);
220void LogReplayWorker::_resetPlaybackToBeginning()
222 if (_logFile.isOpen()) {
223 if (!_logFile.reset()) {
224 qCWarning(LogReplayLinkLog) <<
"failed to reset log file:" << _logFile.error() << _logFile.errorString();
228 _playbackStartTimeMSecs = 0;
229 _playbackStartLogTimeUSecs = 0;
230 _logCurrentTimeUSecs = _logStartTimeUSecs;
233void LogReplayWorker::_readNextLogEntry()
235 int timeToNextExecutionMSecs = 0;
236 while (timeToNextExecutionMSecs < 3) {
238 bytes.reserve(_logFile.bytesAvailable());
239 const qint64 nextTimeUSecs = _readNextMavlinkMessage(bytes);
241 emit
playbackPercentCompleteChanged((
static_cast<float>(_logCurrentTimeUSecs - _logStartTimeUSecs) /
static_cast<float>(_logDurationUSecs)) * 100);
243 if (_logFile.atEnd()) {
249 _logCurrentTimeUSecs = nextTimeUSecs;
251 const quint64 currentTimeMSecs =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch());
252 const quint64 desiredPlayheadMovementTimeMSecs = ((_logCurrentTimeUSecs - _playbackStartLogTimeUSecs) / 1000) / _playbackSpeed;
253 const quint64 desiredCurrentTimeMSecs = _playbackStartTimeMSecs + desiredPlayheadMovementTimeMSecs;
254 timeToNextExecutionMSecs = desiredCurrentTimeMSecs - currentTimeMSecs;
257 _signalCurrentLogTimeSecs();
259 _readTickTimer->start(timeToNextExecutionMSecs);
262void LogReplayWorker::_signalCurrentLogTimeSecs()
267bool LogReplayWorker::_loadLogFile()
269 if (_logFile.isOpen()) {
271 emit
errorOccurred(tr(
"Attempt to load new log while log being played"));
275 const QString logFilename = _logReplayConfig->
logFilename();
276 _logFile.setFileName(logFilename);
277 if (!_logFile.open(QFile::ReadOnly)) {
278 emit
errorOccurred(tr(
"Unable to open log file: '%1', error: %2").arg(logFilename, _logFile.errorString()));
282 QFileInfo logFileInfo;
283 logFileInfo.setFile(logFilename);
284 _logFileSize = logFileInfo.size();
286 const quint64 startTimeUSecs = _parseTimestamp(_logFile.read(kTimestamp));
287 const quint64 endTimeUSecs = _findLastTimestamp();
288 if (endTimeUSecs <= startTimeUSecs) {
290 emit
errorOccurred(tr(
"The log file '%1' is corrupt or empty.").arg(logFilename));
294 _logEndTimeUSecs = endTimeUSecs;
295 _logStartTimeUSecs = startTimeUSecs;
296 _logDurationUSecs = endTimeUSecs - startTimeUSecs;
297 _logCurrentTimeUSecs = startTimeUSecs;
299 if (!_logFile.reset()) {
300 qCWarning(LogReplayLinkLog) <<
"failed to reset log file:" << _logFile.error() << _logFile.errorString();
303 const quint64 logDurationSecondsTotal = _logDurationUSecs / 1000000;
309quint64 LogReplayWorker::_parseTimestamp(
const QByteArray &bytes)
311 const quint64 currentTimestamp =
static_cast<quint64
>(QDateTime::currentMSecsSinceEpoch()) * 1000;
312 quint64 timestamp = qFromBigEndian(*
reinterpret_cast<const quint64*
>(bytes.constData()));
313 if (timestamp > currentTimestamp) {
314 timestamp = qbswap(timestamp);
320quint64 LogReplayWorker::_readNextMavlinkMessage(QByteArray &bytes)
325 while (_logFile.getChar(&nextByte)) {
327 mavlink_status_t status{};
328 const bool messageFound = mavlink_parse_char(_mavlinkChannel, nextByte, &message, &status);
330 if (status.parse_state == MAVLINK_PARSE_STATE_GOT_STX) {
333 (void) bytes.append(nextByte);
336 const QByteArray rawTime = _logFile.read(kTimestamp);
337 return _parseTimestamp(rawTime);
346 mavlink_reset_channel_status(_mavlinkChannel);
348 qint64 messageStartPos = -1;
350 while (_logFile.getChar(&nextByte)) {
351 mavlink_status_t status{};
352 const bool messageFound = mavlink_parse_char(_mavlinkChannel, nextByte, &nextMsg, &status);
354 if (status.parse_state == MAVLINK_PARSE_STATE_GOT_STX) {
355 messageStartPos = _logFile.pos() - 1;
358 if (messageFound && (messageStartPos != -1)) {
359 if (!_logFile.seek(messageStartPos - kTimestamp)) {
360 qCWarning(LogReplayLinkLog) <<
"Failed to seek next message:" << _logFile.error() << _logFile.errorString();
364 const QByteArray rawTime = _logFile.read(kTimestamp);
365 return _parseTimestamp(rawTime);
372quint64 LogReplayWorker::_findLastTimestamp()
374 if (!_logFile.reset()) {
375 qCWarning(LogReplayLinkLog) <<
"failed to reset log file:" << _logFile.error() << _logFile.errorString();
378 mavlink_reset_channel_status(_mavlinkChannel);
380 quint64 lastTimestamp = 0;
382 while (_logFile.bytesAvailable() >
static_cast<qint64
>(kTimestamp)) {
383 lastTimestamp = _parseTimestamp(_logFile.read(kTimestamp));
385 bool endOfMessage =
false;
387 while (!endOfMessage && _logFile.getChar(&nextByte)) {
389 mavlink_status_t status{};
390 endOfMessage = mavlink_parse_char(_mavlinkChannel, nextByte, &msg, &status);
394 return lastTimestamp;
403 , _workerThread(new QThread(this))
405 qCDebug(LogReplayLinkLog) <<
this;
407 _workerThread->setObjectName(QStringLiteral(
"LogReplay_%1").arg(_logReplayConfig->
name()));
409 _worker->moveToThread(_workerThread);
412 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
426 _workerThread->start();
432 (void) QMetaObject::invokeMethod(_worker,
"disconnectFromLog", Qt::BlockingQueuedConnection);
436 _workerThread->quit();
437 if (!_workerThread->wait()) {
438 qCWarning(LogReplayLinkLog) <<
"Failed to wait for LogReplay Thread to close";
441 qCDebug(LogReplayLinkLog) <<
this;
449bool LogReplayLink::_connect()
451 return QMetaObject::invokeMethod(_worker,
"connectToLog", Qt::QueuedConnection);
457 (void) QMetaObject::invokeMethod(_worker,
"disconnectFromLog", Qt::QueuedConnection);
461void LogReplayLink::_onConnected()
463 _disconnectedEmitted =
false;
467void LogReplayLink::_onDisconnected()
469 if (!_disconnectedEmitted.exchange(
true)) {
474void LogReplayLink::_onErrorOccurred(
const QString &
errorString)
476 qCWarning(LogReplayLinkLog) <<
"Error:" <<
errorString;
480void LogReplayLink::_onDataReceived(
const QByteArray &data)
492 (void) QMetaObject::invokeMethod(_worker,
"play", Qt::QueuedConnection);
497 (void) QMetaObject::invokeMethod(_worker,
"pause", Qt::QueuedConnection);
502 (void) QMetaObject::invokeMethod(_worker,
"setPlaybackSpeed", Qt::QueuedConnection, playbackSpeed);
507 (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 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()