Merged in f5soh/librepilot/LP-575_fedora_package (pull request #491)
[librepilot.git] / ground / gcs / src / libs / utils / logfile.cpp
blob95b400d6889d98fa457ecae987cfd8c3b6f748e0
1 /**
2 ******************************************************************************
4 * @file logfile.cpp
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2017.
6 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
7 * @see The GNU Public License (GPL) Version 3
9 *****************************************************************************/
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19 * for more details.
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #include "logfile.h"
27 #include <QDebug>
28 #include <QtGlobal>
30 LogFile::LogFile(QObject *parent) : QIODevice(parent),
31 m_timer(this),
32 m_previousTimeStamp(0),
33 m_nextTimeStamp(0),
34 m_lastPlayed(0),
35 m_timeOffset(0),
36 m_playbackSpeed(1.0),
37 paused(false),
38 m_useProvidedTimeStamp(false),
39 m_providedTimeStamp(0)
41 connect(&m_timer, &QTimer::timeout, this, &LogFile::timerFired);
44 bool LogFile::isSequential() const
46 // returning true fixes "UAVTalk - error : bad type" errors when replaying a log file
47 return true;
50 /**
51 * Opens the logfile QIODevice and the underlying logfile. In case
52 * we want to save the logfile, we open in WriteOnly. In case we
53 * want to read the logfile, we open in ReadOnly.
55 bool LogFile::open(OpenMode mode)
57 // start a timer for playback
58 m_myTime.restart();
59 if (m_file.isOpen()) {
60 // We end up here when doing a replay, because the connection
61 // manager will also try to open the QIODevice, even though we just
62 // opened it after selecting the file, which happens before the
63 // connection manager call...
64 return true;
66 qDebug() << "LogFile - open" << fileName();
68 if (m_file.open(mode) == false) {
69 qWarning() << "Unable to open " << m_file.fileName() << " for logging";
70 return false;
73 // TODO: Write a header at the beginng describing objects so that in future
74 // they can be read back if ID's change
76 // Must call parent function for QIODevice to pass calls to writeData
77 // We always open ReadWrite, because otherwise we will get tons of warnings
78 // during a logfile replay. Read nature is checked upon write ops below.
79 QIODevice::open(QIODevice::ReadWrite);
81 return true;
84 void LogFile::close()
86 qDebug() << "LogFile - close" << fileName();
87 emit aboutToClose();
88 m_file.close();
89 QIODevice::close();
92 qint64 LogFile::writeData(const char *data, qint64 dataSize)
94 if (!m_file.isWritable()) {
95 return dataSize;
98 // If needed, use provided timestamp instead of the GCS timer
99 // This is used when saving logs from on-board logging
100 quint32 timeStamp = m_useProvidedTimeStamp ? m_providedTimeStamp : m_myTime.elapsed();
102 m_file.write((char *)&timeStamp, sizeof(timeStamp));
103 m_file.write((char *)&dataSize, sizeof(dataSize));
105 qint64 written = m_file.write(data, dataSize);
107 // flush (needed to avoid UAVTalk device full errors)
108 m_file.flush();
110 if (written != -1) {
111 emit bytesWritten(written);
114 return dataSize;
117 qint64 LogFile::readData(char *data, qint64 maxlen)
119 QMutexLocker locker(&m_mutex);
121 qint64 len = qMin(maxlen, (qint64)m_dataBuffer.size());
123 if (len) {
124 memcpy(data, m_dataBuffer.data(), len);
125 m_dataBuffer.remove(0, len);
128 return len;
131 qint64 LogFile::bytesAvailable() const
133 QMutexLocker locker(&m_mutex);
135 qint64 len = m_dataBuffer.size();
137 return len;
140 void LogFile::timerFired()
142 if (m_file.bytesAvailable() > 4) {
143 int time;
144 time = m_myTime.elapsed();
146 // TODO: going back in time will be a problem
147 while ((m_lastPlayed + ((double)(time - m_timeOffset) * m_playbackSpeed) > m_nextTimeStamp)) {
148 m_lastPlayed += ((double)(time - m_timeOffset) * m_playbackSpeed);
150 // read data size
151 qint64 dataSize;
152 if (m_file.bytesAvailable() < (qint64)sizeof(dataSize)) {
153 qDebug() << "LogFile - end of log file reached";
154 stopReplay();
155 return;
157 m_file.read((char *)&dataSize, sizeof(dataSize));
159 // check size consistency
160 if (dataSize < 1 || dataSize > (1024 * 1024)) {
161 qWarning() << "LogFile - corrupted log file! Unlikely packet size:" << dataSize;
162 stopReplay();
163 return;
166 // read data
167 if (m_file.bytesAvailable() < dataSize) {
168 qDebug() << "LogFile - end of log file reached";
169 stopReplay();
170 return;
172 QByteArray data = m_file.read(dataSize);
174 // make data available
175 m_mutex.lock();
176 m_dataBuffer.append(data);
177 m_mutex.unlock();
179 emit readyRead();
181 // read next timestamp
182 if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
183 qDebug() << "LogFile - end of log file reached";
184 stopReplay();
185 return;
187 m_previousTimeStamp = m_nextTimeStamp;
188 m_file.read((char *)&m_nextTimeStamp, sizeof(m_nextTimeStamp));
190 // some validity checks
191 if ((m_nextTimeStamp < m_previousTimeStamp) // logfile goes back in time
192 || ((m_nextTimeStamp - m_previousTimeStamp) > 60 * 60 * 1000)) { // gap of more than 60 minutes
193 qWarning() << "LogFile - corrupted log file! Unlikely timestamp:" << m_nextTimeStamp << "after" << m_previousTimeStamp;
194 stopReplay();
195 return;
198 m_timeOffset = time;
199 time = m_myTime.elapsed();
201 } else {
202 qDebug() << "LogFile - end of log file reached";
203 stopReplay();
207 bool LogFile::isPlaying() const
209 return m_file.isOpen() && m_timer.isActive();
212 bool LogFile::startReplay()
214 if (!m_file.isOpen() || m_timer.isActive()) {
215 return false;
217 qDebug() << "LogFile - startReplay";
219 m_myTime.restart();
220 m_timeOffset = 0;
221 m_lastPlayed = 0;
222 m_previousTimeStamp = 0;
223 m_nextTimeStamp = 0;
224 m_dataBuffer.clear();
226 // read next timestamp
227 if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) {
228 qWarning() << "LogFile - invalid log file!";
229 return false;
231 m_file.read((char *)&m_nextTimeStamp, sizeof(m_nextTimeStamp));
233 m_timer.setInterval(10);
234 m_timer.start();
235 paused = false;
237 emit replayStarted();
238 return true;
241 bool LogFile::stopReplay()
243 if (!m_file.isOpen() || !(m_timer.isActive() || paused)) {
244 return false;
246 qDebug() << "LogFile - stopReplay";
247 m_timer.stop();
248 paused = false;
250 emit replayFinished();
251 return true;
254 bool LogFile::pauseReplay()
256 if (!m_timer.isActive()) {
257 return false;
259 qDebug() << "LogFile - pauseReplay";
260 m_timer.stop();
261 paused = true;
263 // hack to notify UI that replay paused
264 emit replayStarted();
265 return true;
268 bool LogFile::resumeReplay()
270 if (m_timer.isActive()) {
271 return false;
273 qDebug() << "LogFile - resumeReplay";
274 m_timeOffset = m_myTime.elapsed();
275 m_timer.start();
276 paused = false;
278 // hack to notify UI that replay resumed
279 emit replayStarted();
280 return true;