diff -Nru librepilot-16.09+r711~ga4c0bcf/CREDITS.txt librepilot-16.09+r735~gaba11f0/CREDITS.txt --- librepilot-16.09+r711~ga4c0bcf/CREDITS.txt 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/CREDITS.txt 2018-09-29 10:02:39.000000000 +0000 @@ -34,7 +34,7 @@ Kevin Finisterre Richard Flay Darren Furniss -Oliver Gaste +Olivier Gasté Cliff Geerdes Frederic Goddeeris Daniel Godin @@ -72,6 +72,7 @@ Matt Lipski David Llama Jasper Van Loenen +Fernando Lopez Jr. Ákos Máté Ben Matthews Greg Matthews @@ -80,9 +81,12 @@ Gary Mortimer Cathy Moss Les Newell +Karsten Telling Nielsen +Jan Nijs Ken Northup Craig Nuttall Bertrand Oresve +Pashalis Padeleris Angus Peart Pablo Francisco Pérez Hidalgo John Pike @@ -130,4 +134,3 @@ David Willis Dmitriy Zaitsev Vladimir Zidar - diff -Nru librepilot-16.09+r711~ga4c0bcf/debian/changelog librepilot-16.09+r735~gaba11f0/debian/changelog --- librepilot-16.09+r711~ga4c0bcf/debian/changelog 2018-04-30 01:18:00.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/debian/changelog 2018-09-29 14:28:52.000000000 +0000 @@ -1,5 +1,5 @@ -librepilot (16.09+r711~ga4c0bcf-0xenial1) xenial; urgency=low +librepilot (16.09+r735~gaba11f0-0xenial1) xenial; urgency=low * Release from upstream Git repository - -- The LibrePilot Project Mon, 30 Apr 2018 03:06:40 +0200 + -- The LibrePilot Project Sat, 29 Sep 2018 16:28:42 +0200 Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_coptercontrol/fw_coptercontrol.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_coptercontrol/fw_coptercontrol.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_gpsplatinum/fw_gpsplatinum.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_gpsplatinum/fw_gpsplatinum.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_nucleof303re/fw_nucleof303re.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_nucleof303re/fw_nucleof303re.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_oplinkmini/fw_oplinkmini.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_oplinkmini/fw_oplinkmini.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_osd/fw_osd.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_osd/fw_osd.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_pikoblx/fw_pikoblx.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_pikoblx/fw_pikoblx.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_revolution/fw_revolution.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_revolution/fw_revolution.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_revonano/fw_revonano.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_revonano/fw_revonano.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_revoproto/fw_revoproto.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_revoproto/fw_revoproto.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_sparky2/fw_sparky2.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_sparky2/fw_sparky2.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_spracingf3/fw_spracingf3.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_spracingf3/fw_spracingf3.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_spracingf3evo/fw_spracingf3evo.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_spracingf3evo/fw_spracingf3evo.opfw differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/firmware/fw_tinyfish/fw_tinyfish.opfw and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/firmware/fw_tinyfish/fw_tinyfish.opfw differ diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/libs/opmapcontrol/src/core/providerstrings.cpp librepilot-16.09+r735~gaba11f0/ground/gcs/src/libs/opmapcontrol/src/core/providerstrings.cpp --- librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/libs/opmapcontrol/src/core/providerstrings.cpp 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/gcs/src/libs/opmapcontrol/src/core/providerstrings.cpp 2018-09-29 10:02:39.000000000 +0000 @@ -41,7 +41,7 @@ { // Google version strings VersionGoogleMap = "m@301"; - QString version = "794"; + QString version = "810"; QString envVersion = qgetenv("GCS_GOOGLE_SAT_VERSION").constData(); VersionGoogleSatellite = (envVersion.toInt() > version.toInt()) ? envVersion : version; VersionGoogleLabels = "h@301"; diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/libs/utils/logfile.cpp librepilot-16.09+r735~gaba11f0/ground/gcs/src/libs/utils/logfile.cpp --- librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/libs/utils/logfile.cpp 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/gcs/src/libs/utils/logfile.cpp 2018-09-29 10:02:39.000000000 +0000 @@ -2,7 +2,7 @@ ****************************************************************************** * * @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. * @see The GNU Public License (GPL) Version 3 * @@ -26,6 +26,9 @@ #include #include +#include + +#define TIMESTAMP_SIZE_BYTES 4 LogFile::LogFile(QObject *parent) : QIODevice(parent), m_timer(this), @@ -34,9 +37,12 @@ m_lastPlayed(0), m_timeOffset(0), m_playbackSpeed(1.0), - paused(false), + m_replayState(STOPPED), 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); } @@ -137,36 +143,61 @@ return len; } +/** + timerFired() + + This function is called at a 10 ms interval to fill the replay buffers. + + */ void LogFile::timerFired() { - if (m_file.bytesAvailable() > 4) { + if (m_replayState != PLAYING) { + return; + } + m_timerTick++; + + if (m_file.bytesAvailable() > TIMESTAMP_SIZE_BYTES) { int time; 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); // read data size qint64 dataSize; if (m_file.bytesAvailable() < (qint64)sizeof(dataSize)) { - qDebug() << "LogFile - end of log file reached"; - stopReplay(); + qDebug() << "LogFile replay - end of log file reached"; + resetReplay(); return; } m_file.read((char *)&dataSize, sizeof(dataSize)); // check size consistency 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(); return; } // read data if (m_file.bytesAvailable() < dataSize) { - qDebug() << "LogFile - end of log file reached"; - stopReplay(); + qDebug() << "LogFile replay - end of log file reached"; + resetReplay(); return; } QByteArray data = m_file.read(dataSize); @@ -178,10 +209,14 @@ 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 if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { - qDebug() << "LogFile - end of log file reached"; - stopReplay(); + qDebug() << "LogFile replay - end of log file reached"; + resetReplay(); return; } m_previousTimeStamp = m_nextTimeStamp; @@ -190,17 +225,17 @@ // some validity checks if ((m_nextTimeStamp < m_previousTimeStamp) // logfile goes back in time || ((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(); return; } m_timeOffset = time; - time = m_myTime.elapsed(); + time = m_myTime.elapsed(); // number of milliseconds since start of playback } } else { - qDebug() << "LogFile - end of log file reached"; - stopReplay(); + qDebug() << "LogFile replay - end of log file reached"; + resetReplay(); } } @@ -209,8 +244,26 @@ 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() { + // 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()) { return false; } @@ -221,7 +274,9 @@ m_lastPlayed = 0; m_previousTimeStamp = 0; m_nextTimeStamp = 0; + m_mutex.lock(); m_dataBuffer.clear(); + m_mutex.unlock(); // read next timestamp if (m_file.bytesAvailable() < (qint64)sizeof(m_nextTimeStamp)) { @@ -232,50 +287,275 @@ m_timer.setInterval(10); m_timer.start(); - paused = false; + m_replayState = PLAYING; emit replayStarted(); 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() { - if (!m_file.isOpen() || !(m_timer.isActive() || paused)) { + if (!m_file.isOpen()) { return false; } + if (m_timer.isActive()) { + m_timer.stop(); + } + qDebug() << "LogFile - stopReplay"; - m_timer.stop(); - paused = false; + m_replayState = STOPPED; emit replayFinished(); return true; } -bool LogFile::pauseReplay() +/** + * 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_timer.isActive()) { + if (!m_file.isOpen()) { return false; } - qDebug() << "LogFile - pauseReplay"; - m_timer.stop(); - paused = true; + if (m_timer.isActive()) { + m_timer.stop(); + } - // hack to notify UI that replay paused - emit replayStarted(); + qDebug() << "LogFile - resetReplay"; + m_replayState = STOPPED; + + emit replayCompleted(); return true; } -bool LogFile::resumeReplay() +/** + * 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"; - m_timeOffset = m_myTime.elapsed(); + + // 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(); - paused = false; - // hack to notify UI that replay resumed + // Notify UI that playback has resumed emit replayStarted(); return true; } + +/** + * SLOT: pauseReplay() + * + * Pauses replay while storing the current playback position + * + */ +bool LogFile::pauseReplay() +{ + if (!m_timer.isActive()) { + return false; + } + qDebug() << "LogFile - pauseReplay"; + m_timer.stop(); + m_replayState = PAUSED; + return true; +} + +/** + * SLOT: pauseReplayAndResetPosition() + * + * Pauses replay and resets the playback position to the start of the logfile + * + */ +bool LogFile::pauseReplayAndResetPosition() +{ + if (!m_file.isOpen() || !m_timer.isActive()) { + return false; + } + qDebug() << "LogFile - pauseReplayAndResetPosition"; + m_timer.stop(); + m_replayState = STOPPED; + + 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); + + return true; +} diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/libs/utils/logfile.h librepilot-16.09+r735~gaba11f0/ground/gcs/src/libs/utils/logfile.h --- librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/libs/utils/logfile.h 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/gcs/src/libs/utils/logfile.h 2018-09-29 10:02:39.000000000 +0000 @@ -2,7 +2,7 @@ ****************************************************************************** * * @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. * @see The GNU Public License (GPL) Version 3 * @@ -32,8 +32,10 @@ #include #include #include -#include #include +#include + +typedef enum { PLAYING, PAUSED, STOPPED } ReplayState; class QTCREATOR_UTILS_EXPORT LogFile : public QIODevice { Q_OBJECT @@ -75,6 +77,8 @@ m_providedTimeStamp = providedTimestamp; } + ReplayState getReplayState(); + public slots: void setReplaySpeed(double val) { @@ -83,24 +87,28 @@ }; bool startReplay(); bool stopReplay(); + + bool resumeReplay(quint32); bool pauseReplay(); - bool resumeReplay(); + bool pauseReplayAndResetPosition(); protected slots: void timerFired(); signals: - void readReady(); 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: QByteArray m_dataBuffer; QTimer m_timer; QTime m_myTime; QFile m_file; - qint32 m_previousTimeStamp; - qint32 m_nextTimeStamp; + quint32 m_previousTimeStamp; + quint32 m_nextTimeStamp; double m_lastPlayed; // QMutex wants to be mutable // http://stackoverflow.com/questions/25521570/can-mutex-locking-function-be-marked-as-const @@ -108,11 +116,19 @@ int m_timeOffset; double m_playbackSpeed; - bool paused; + ReplayState m_replayState; private: bool m_useProvidedTimeStamp; qint32 m_providedTimeStamp; + quint32 m_beginTimeStamp; + quint32 m_endTimeStamp; + quint32 m_timerTick; + QVector m_timeStamps; + QVector m_timeStampPositions; + + bool buildIndex(); + bool resetReplay(); }; #endif // LOGFILE_H Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/images/pause.png and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/images/pause.png differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/images/play.png and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/images/play.png differ Binary files /tmp/tmpe9TNnr/N1wqLca41q/librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/images/stop.png and /tmp/tmpe9TNnr/ZmT1lbNLqx/librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/images/stop.png differ diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp --- librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/logginggadgetwidget.cpp 2018-09-29 10:02:39.000000000 +0000 @@ -1,13 +1,14 @@ /** ****************************************************************************** * - * @file GCSControlgadgetwidget.cpp - * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010. + * @file logginggadgetwidget.cpp + * @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 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 @@ -40,6 +41,14 @@ ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); scpPlugin = pm->getObject(); + + 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() @@ -50,20 +59,82 @@ void LoggingGadgetWidget::setPlugin(LoggingPlugin *p) { loggingPlugin = p; + LogFile *logFile = loggingPlugin->getLogfile(); - connect(m_logging->playButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::startPlotting); - connect(m_logging->pauseButton, &QPushButton::clicked, scpPlugin, &ScopeGadgetFactory::stopPlotting); + // 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); - LogFile *logFile = loggingPlugin->getLogfile(); - connect(m_logging->playButton, &QPushButton::clicked, logFile, &LogFile::resumeReplay); - connect(m_logging->pauseButton, &QPushButton::clicked, logFile, &LogFile::pauseReplay); connect(m_logging->playbackSpeed, static_cast(&QDoubleSpinBox::valueChanged), logFile, &LogFile::setReplaySpeed); + // 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()); } +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) { QString status; @@ -71,7 +142,8 @@ switch (state) { case LoggingPlugin::IDLE: - status = tr("Idle"); + status = tr("Idle"); + setPlaybackPosition(0); break; case LoggingPlugin::LOGGING: status = tr("Logging"); @@ -84,10 +156,126 @@ m_logging->statusLabel->setText(status); bool playing = loggingPlugin->getLogfile()->isPlaying(); - m_logging->playButton->setEnabled(enabled && !playing); - m_logging->pauseButton->setEnabled(enabled && playing); + m_logging->playButton->setEnabled(enabled); + 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()); +} + + /** * @} * @} diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/logginggadgetwidget.h librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/logginggadgetwidget.h --- librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/logginggadgetwidget.h 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/logginggadgetwidget.h 2018-09-29 10:02:39.000000000 +0000 @@ -1,13 +1,14 @@ /** ****************************************************************************** * - * @file GCSControlgadgetwidget.h - * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010. + * @file logginggadgetwidget.h + * @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 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 @@ -37,6 +38,7 @@ #include class Ui_Logging; +class QTimer; class LoggingGadgetWidget : public QWidget { Q_OBJECT @@ -48,15 +50,28 @@ protected slots: 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: - void pause(); - void play(); + void resumeReplay(quint32 positionTimeStamp); + void pauseReplay(); + void pauseReplayAndResetPosition(); private: Ui_Logging *m_logging; LoggingPlugin *loggingPlugin; ScopeGadgetFactory *scpPlugin; + QTimer sliderActionDelay; + + void updatePositionLabel(quint32 positionTimeStamp); }; #endif /* LoggingGADGETWIDGET_H_ */ diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/logging.pro librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/logging.pro --- librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/logging.pro 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/logging.pro 2018-09-29 10:02:39.000000000 +0000 @@ -24,3 +24,5 @@ FORMS += logging.ui +RESOURCES += \ + res.qrc diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/logging.ui librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/logging.ui --- librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/logging.ui 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/logging.ui 2018-09-29 10:02:39.000000000 +0000 @@ -7,75 +7,156 @@ 0 0 439 - 122 + 120 - 100 - 80 + 1 + 1 100 - 80 + 118 Form - + - + + + 3 + - + + + 4 + QLayout::SetNoConstraint + + true + - + 0 0 - 30 - 0 + 39 + 30 - - Play + + + 39 + 30 + + + + Qt::NoFocus + + + false - - :/notify/images/play.png:/notify/images/play.png + + :/logging/images/play.png:/logging/images/play.png + + + + 16 + 16 + - + 0 0 - 30 - 0 + 39 + 30 - - Pause + + + 39 + 30 + + + + + :/logging/images/pause.png:/logging/images/pause.png + + + + 16 + 16 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 4 + 20 + + + + + + + + + 0 + 0 + + + + + 39 + 30 + + + + + 39 + 30 + - - :/notify/images/stop.png:/notify/images/stop.png + + :/logging/images/stop.png:/logging/images/stop.png + + + + 16 + 16 + @@ -93,6 +174,54 @@ + + + Playback speed: + + + true + + + + + + + + 0 + 0 + + + + 1 + + + 0.100000000000000 + + + 10.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + Status: @@ -101,6 +230,12 @@ + + + 65 + 0 + + 75 @@ -115,29 +250,85 @@ - + + + true + + + Qt::Horizontal + + + + + + + 0 + + + 0 + + + 0 + - + + + + 0 + 0 + + + + + 40 + 0 + + - Playback speed: + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - 10.000000000000000 + + + Qt::Horizontal - - 0.100000000000000 + + + 40 + 20 + - - 1.000000000000000 + + + + + + + 0 + 0 + + + + + 40 + 0 + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - + Qt::Horizontal @@ -149,6 +340,28 @@ + + + + + 0 + 0 + + + + + 40 + 0 + + + + + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + @@ -161,7 +374,7 @@ 20 - 40 + 5 @@ -169,7 +382,7 @@ - + diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/res.qrc librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/res.qrc --- librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/logging/res.qrc 1970-01-01 00:00:00.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/logging/res.qrc 2018-09-29 10:02:39.000000000 +0000 @@ -0,0 +1,7 @@ + + + images/play.png + images/pause.png + images/stop.png + + diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/uavobjects/uavobject.h.template librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/uavobjects/uavobject.h.template --- librepilot-16.09+r711~ga4c0bcf/ground/gcs/src/plugins/uavobjects/uavobject.h.template 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/gcs/src/plugins/uavobjects/uavobject.h.template 2018-09-29 10:02:39.000000000 +0000 @@ -35,6 +35,7 @@ #define $(NAMEUC)_H #include "uavdataobject.h" +$(INCLUDE) class UAVObjectManager; diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/uavobjgenerator/generators/flight/uavobjectgeneratorflight.cpp librepilot-16.09+r735~gaba11f0/ground/uavobjgenerator/generators/flight/uavobjectgeneratorflight.cpp --- librepilot-16.09+r711~ga4c0bcf/ground/uavobjgenerator/generators/flight/uavobjectgeneratorflight.cpp 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/uavobjgenerator/generators/flight/uavobjectgeneratorflight.cpp 2018-09-29 10:02:39.000000000 +0000 @@ -24,6 +24,7 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include #include "uavobjectgeneratorflight.h" using namespace std; @@ -169,52 +170,91 @@ enums.append(QString("/* Field %1 information */\n").arg(info->fields[n]->name)); // Only for enum types if (info->fields[n]->type == FIELDTYPE_ENUM) { - enums.append(QString("\n// Enumeration options for field %1\n").arg(info->fields[n]->name)); - enums.append("typedef enum __attribute__ ((__packed__)) {\n"); - // Go through each option - QStringList options = info->fields[n]->options; - for (int m = 0; m < options.length(); ++m) { - QString s = (m == (options.length() - 1)) ? " %1_%2_%3=%4\n" : " %1_%2_%3=%4,\n"; - enums.append(s - .arg(info->name.toUpper()) - .arg(info->fields[n]->name.toUpper()) - .arg(options[m].toUpper().replace(QRegExp(ENUM_SPECIAL_CHARS), "")) - .arg(m)); + if (info->fields[n]->parentObjectName.length() > 0) { + enums.append(QString("typedef %1%2Options %3%4Options;\n") + .arg(info->fields[n]->parentObjectName) + .arg(info->fields[n]->parentFieldName) + .arg(info->name) + .arg(info->fields[n]->name)); + } else { + enums.append(QString("\n// Enumeration options for field %1\n").arg(info->fields[n]->name)); + enums.append("typedef enum __attribute__ ((__packed__)) {\n"); + // Go through each option + QStringList options = info->fields[n]->options; + for (int m = 0; m < options.length(); ++m) { + QString s = (m == (options.length() - 1)) ? " %1_%2_%3=%4\n" : " %1_%2_%3=%4,\n"; + enums.append(s + .arg(info->name.toUpper()) + .arg(info->fields[n]->name.toUpper()) + .arg(options[m].toUpper().replace(QRegExp(ENUM_SPECIAL_CHARS), "")) + .arg(m)); + } + enums.append(QString("} %1%2Options;\n") + .arg(info->name) + .arg(info->fields[n]->name)); } - enums.append(QString("} %1%2Options;\n") - .arg(info->name) - .arg(info->fields[n]->name)); } // Generate element names (only if field has more than one element) if (info->fields[n]->numElements > 1 && !info->fields[n]->defaultElementNames) { - enums.append(QString("\n// Array element names for field %1\n").arg(info->fields[n]->name)); - enums.append("typedef enum {\n"); - // Go through the element names - QStringList elemNames = info->fields[n]->elementNames; - for (int m = 0; m < elemNames.length(); ++m) { - QString s = (m != (elemNames.length() - 1)) ? " %1_%2_%3=%4,\n" : " %1_%2_%3=%4\n"; - enums.append(s - .arg(info->name.toUpper()) - .arg(info->fields[n]->name.toUpper()) - .arg(elemNames[m].toUpper()) - .arg(m)); + if (info->fields[n]->parentObjectName.length() > 0) { + enums.append(QString("typedef %1%2Elem %3%4Elem;\n") + .arg(info->fields[n]->parentObjectName) + .arg(info->fields[n]->parentFieldName) + .arg(info->name) + .arg(info->fields[n]->name)); + } else { + enums.append(QString("\n// Array element names for field %1\n").arg(info->fields[n]->name)); + enums.append("typedef enum {\n"); + // Go through the element names + QStringList elemNames = info->fields[n]->elementNames; + for (int m = 0; m < elemNames.length(); ++m) { + QString s = (m != (elemNames.length() - 1)) ? " %1_%2_%3=%4,\n" : " %1_%2_%3=%4\n"; + enums.append(s + .arg(info->name.toUpper()) + .arg(info->fields[n]->name.toUpper()) + .arg(elemNames[m].toUpper()) + .arg(m)); + } + enums.append(QString("} %1%2Elem;\n") + .arg(info->name) + .arg(info->fields[n]->name)); } - enums.append(QString("} %1%2Elem;\n") - .arg(info->name) - .arg(info->fields[n]->name)); } // Generate array information if (info->fields[n]->numElements > 1) { enums.append(QString("\n// Number of elements for field %1\n").arg(info->fields[n]->name)); - enums.append(QString("#define %1_%2_NUMELEM %3\n") - .arg(info->name.toUpper()) - .arg(info->fields[n]->name.toUpper()) - .arg(info->fields[n]->numElements)); + if (info->fields[n]->parentObjectName.length() > 0) { + enums.append(QString("#define %1_%2_NUMELEM %3_%4_NUMELEM\n") + .arg(info->name.toUpper()) + .arg(info->fields[n]->name.toUpper()) + .arg(info->fields[n]->parentObjectName.toUpper()) + .arg(info->fields[n]->parentFieldName.toUpper())); + } else { + enums.append(QString("#define %1_%2_NUMELEM %3\n") + .arg(info->name.toUpper()) + .arg(info->fields[n]->name.toUpper()) + .arg(info->fields[n]->numElements)); + } } enums.append(QString("\n")); } + + QString includes; + QSet parentObjects; + + for (int n = 0; n < info->fields.length(); ++n) { + if (info->fields[n]->parentObjectName.length() > 0) { + parentObjects.insert(info->fields[n]->parentObjectName); + } + } + + foreach(const QString &objectName, parentObjects) { + includes.append("#include \"" + objectName.toLower() + ".h\"\n"); + } + outInclude.replace(QString("$(DATAFIELDINFO)"), enums); + outInclude.replace(QString("$(INCLUDE)"), includes); // Replace the $(INITFIELDS) tag QString initfields; diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/uavobjgenerator/generators/gcs/uavobjectgeneratorgcs.cpp librepilot-16.09+r735~gaba11f0/ground/uavobjgenerator/generators/gcs/uavobjectgeneratorgcs.cpp --- librepilot-16.09+r711~ga4c0bcf/ground/uavobjgenerator/generators/gcs/uavobjectgeneratorgcs.cpp 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/uavobjgenerator/generators/gcs/uavobjectgeneratorgcs.cpp 2018-09-29 10:02:39.000000000 +0000 @@ -24,7 +24,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - +#include #include "uavobjectgeneratorgcs.h" #define VERBOSE false @@ -52,27 +52,32 @@ } struct Context { - ObjectInfo *object; + ObjectInfo *object; + // parent objects + QSet parentObjects; // enums - QString enums; - QString enumsCount; - QString registerImpl; + QString enums; + QString enumsCount; + QString registerImpl; // interface - QString fields; - QString fieldsInfo; - QString properties; - QString deprecatedProperties; - QString getters; - QString setters; - QString notifications; + QString fields; + QString fieldsInfo; + QString properties; + QString deprecatedProperties; + QString getters; + QString setters; + QString notifications; // implementation - QString fieldsInit; - QString fieldsDefault; - QString propertiesImpl; - QString notificationsImpl; + QString fieldsInit; + QString fieldsDefault; + QString propertiesImpl; + QString notificationsImpl; }; struct FieldContext { + FieldContext(FieldInfo *fieldInfo, ObjectInfo *object); + FieldContext(const FieldContext &fieldContext); + FieldInfo *field; // field QString fieldName; @@ -82,6 +87,10 @@ QString ucPropName; QString propType; QString propRefType; + // + QString parentClassName; + QString parentFieldName; + QString ucParentPropName; // deprecation bool hasDeprecatedProperty; bool hasDeprecatedGetter; @@ -226,6 +235,10 @@ str.replace(":elementCount", QString::number(fieldCtxt.field->numElements)); str.replace(":enumCount", QString::number(fieldCtxt.field->numOptions)); + str.replace(":parentFieldName", fieldCtxt.parentFieldName); + str.replace(":parentClassName", fieldCtxt.parentClassName); + str.replace(":ParentPropName", fieldCtxt.ucParentPropName); + return str; } @@ -234,34 +247,42 @@ ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " // :fieldName\n"); if (fieldCtxt.field->type == FIELDTYPE_ENUM) { - QStringList options = fieldCtxt.field->options; - ctxt.fieldsInfo += " typedef enum { "; - for (int m = 0; m < options.length(); ++m) { - if (m > 0) { - ctxt.fieldsInfo.append(", "); + if (fieldCtxt.field->parentObjectName.length() > 0) { + ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " typedef :parentClassName:::parentFieldNameOptions :fieldNameOptions;\n"); + } else { + QStringList options = fieldCtxt.field->options; + ctxt.fieldsInfo += " typedef enum { "; + for (int m = 0; m < options.length(); ++m) { + if (m > 0) { + ctxt.fieldsInfo.append(", "); + } + ctxt.fieldsInfo += generate(ctxt, fieldCtxt, "%1_%2=%3") + .arg(fieldCtxt.field->name.toUpper()) + .arg(options[m].toUpper().replace(QRegExp(ENUM_SPECIAL_CHARS), "")) + .arg(m); } - ctxt.fieldsInfo += generate(ctxt, fieldCtxt, "%1_%2=%3") - .arg(fieldCtxt.field->name.toUpper()) - .arg(options[m].toUpper().replace(QRegExp(ENUM_SPECIAL_CHARS), "")) - .arg(m); + ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " } :fieldNameOptions;\n"); } - ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " } :fieldNameOptions;\n"); } // Generate element names (only if field has more than one element) if (fieldCtxt.field->numElements > 1 && !fieldCtxt.field->defaultElementNames) { - QStringList elemNames = fieldCtxt.field->elementNames; - ctxt.fieldsInfo += " typedef enum { "; - for (int m = 0; m < elemNames.length(); ++m) { - if (m > 0) { - ctxt.fieldsInfo.append(", "); + if (fieldCtxt.field->parentObjectName.length() > 0) { + ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " typedef :parentClassName:::parentFieldNameElem :fieldNameElem;\n"); + } else { + QStringList elemNames = fieldCtxt.field->elementNames; + ctxt.fieldsInfo += " typedef enum { "; + for (int m = 0; m < elemNames.length(); ++m) { + if (m > 0) { + ctxt.fieldsInfo.append(", "); + } + ctxt.fieldsInfo += QString("%1_%2=%3") + .arg(fieldCtxt.field->name.toUpper()) + .arg(elemNames[m].toUpper()) + .arg(m); } - ctxt.fieldsInfo += QString("%1_%2=%3") - .arg(fieldCtxt.field->name.toUpper()) - .arg(elemNames[m].toUpper()) - .arg(m); + ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " } :fieldNameElem;\n"); } - ctxt.fieldsInfo += generate(ctxt, fieldCtxt, " } :fieldNameElem;\n"); } // Generate array information @@ -341,6 +362,9 @@ void generateField(Context &ctxt, FieldContext &fieldCtxt) { + if (fieldCtxt.parentClassName.length() > 0) { + ctxt.parentObjects.insert(fieldCtxt.parentClassName); + } if (fieldCtxt.field->numElements > 1) { ctxt.fields += generate(ctxt, fieldCtxt, " :fieldType :fieldName[:elementCount];\n"); } else { @@ -355,20 +379,24 @@ { Q_ASSERT(fieldCtxt.field->type == FIELDTYPE_ENUM); - QString enumStringList = toEnumStringList(ctxt.object, fieldCtxt.field); + if (fieldCtxt.field->parentObjectName.length() > 0) { + ctxt.enums += generate(ctxt, fieldCtxt, "typedef :parentClassName_:ParentPropName :ClassName_:PropName;\n\n"); + } else { + QString enumStringList = toEnumStringList(ctxt.object, fieldCtxt.field); - ctxt.enums += generate(ctxt, fieldCtxt, - "class :ClassName_:PropName : public QObject {\n" - " Q_OBJECT\n" - "public:\n" - " enum Enum { %1 };\n" - " Q_ENUMS(Enum) // TODO switch to Q_ENUM once on Qt 5.5\n" - "};\n\n").arg(enumStringList); + ctxt.enums += generate(ctxt, fieldCtxt, + "class :ClassName_:PropName : public QObject {\n" + " Q_OBJECT\n" + "public:\n" + " enum Enum { %1 };\n" + " Q_ENUMS(Enum) // TODO switch to Q_ENUM once on Qt 5.5\n" + "};\n\n").arg(enumStringList); - ctxt.enumsCount += generate(ctxt, fieldCtxt, ":PropNameCount = :enumCount, "); + ctxt.enumsCount += generate(ctxt, fieldCtxt, ":PropNameCount = :enumCount, "); - ctxt.registerImpl += generate(ctxt, fieldCtxt, - " qmlRegisterType<:ClassName_:PropName>(\"%1.:ClassName\", 1, 0, \":PropName\");\n").arg("UAVTalk"); + ctxt.registerImpl += generate(ctxt, fieldCtxt, + " qmlRegisterType<:ClassName_:PropName>(\"%1.:ClassName\", 1, 0, \":PropName\");\n").arg("UAVTalk"); + } } void generateBaseProperty(Context &ctxt, FieldContext &fieldCtxt) @@ -507,14 +535,10 @@ sep = "_"; } - FieldContext elementCtxt; - elementCtxt.field = fieldCtxt.field; - elementCtxt.fieldName = fieldCtxt.fieldName + "_" + elementName; - elementCtxt.fieldType = fieldCtxt.fieldType; - elementCtxt.propName = fieldCtxt.propName + sep + elementName; - elementCtxt.ucPropName = fieldCtxt.ucPropName + sep + elementName; - elementCtxt.propType = fieldCtxt.propType; - elementCtxt.propRefType = fieldCtxt.propRefType; + FieldContext elementCtxt(fieldCtxt); + elementCtxt.fieldName = fieldCtxt.fieldName + "_" + elementName; + elementCtxt.propName = fieldCtxt.propName + sep + elementName; + elementCtxt.ucPropName = fieldCtxt.ucPropName + sep + elementName; // deprecation elementCtxt.hasDeprecatedProperty = (elementCtxt.fieldName != elementCtxt.propName) && DEPRECATED; elementCtxt.hasDeprecatedGetter = DEPRECATED; @@ -593,6 +617,44 @@ return true; } +FieldContext::FieldContext(const FieldContext &fieldContext) +{ + *this = fieldContext; +} + +FieldContext::FieldContext(FieldInfo *fieldInfo, ObjectInfo *object) +{ + field = fieldInfo; + + // field properties + fieldName = field->name; + fieldType = fieldTypeStrCPP(field->type); + + parentClassName = field->parentObjectName; + parentFieldName = field->parentFieldName; + ucParentPropName = toPropertyName(field->parentFieldName); + + ucPropName = toPropertyName(field->name); + propName = toLowerCamelCase(ucPropName); + propType = fieldType; + if (field->type == FIELDTYPE_INT8) { + propType = fieldTypeStrCPP(FIELDTYPE_INT16); + } else if (field->type == FIELDTYPE_UINT8) { + propType = fieldTypeStrCPP(FIELDTYPE_UINT16); + } else if (field->type == FIELDTYPE_ENUM) { + QString enumClassName = object->name + "_" + ucPropName; + propType = enumClassName + "::Enum"; + } + // reference type + propRefType = propType; + + // deprecation + hasDeprecatedProperty = (fieldName != propName) && DEPRECATED; + hasDeprecatedGetter = DEPRECATED; + hasDeprecatedSetter = ((fieldName != ucPropName) || (fieldType != propType)) && DEPRECATED; + hasDeprecatedNotification = ((fieldName != propName) || (fieldType != propType)) && DEPRECATED; +} + /** * Generate the GCS object files * @@ -634,32 +696,7 @@ FieldInfo *field = object->fields[n]; // field context - FieldContext fieldCtxt; - fieldCtxt.field = field; - - // field properties - fieldCtxt.fieldName = field->name; - fieldCtxt.fieldType = fieldTypeStrCPP(field->type); - - fieldCtxt.ucPropName = toPropertyName(field->name); - fieldCtxt.propName = toLowerCamelCase(fieldCtxt.ucPropName); - fieldCtxt.propType = fieldCtxt.fieldType; - if (field->type == FIELDTYPE_INT8) { - fieldCtxt.propType = fieldTypeStrCPP(FIELDTYPE_INT16); - } else if (field->type == FIELDTYPE_UINT8) { - fieldCtxt.propType = fieldTypeStrCPP(FIELDTYPE_UINT16); - } else if (field->type == FIELDTYPE_ENUM) { - QString enumClassName = object->name + "_" + fieldCtxt.ucPropName; - fieldCtxt.propType = enumClassName + "::Enum"; - } - // reference type - fieldCtxt.propRefType = fieldCtxt.propType; - - // deprecation - fieldCtxt.hasDeprecatedProperty = (fieldCtxt.fieldName != fieldCtxt.propName) && DEPRECATED; - fieldCtxt.hasDeprecatedGetter = DEPRECATED; - fieldCtxt.hasDeprecatedSetter = ((fieldCtxt.fieldName != fieldCtxt.ucPropName) || (fieldCtxt.fieldType != fieldCtxt.propType)) && DEPRECATED; - fieldCtxt.hasDeprecatedNotification = ((fieldCtxt.fieldName != fieldCtxt.propName) || (fieldCtxt.fieldType != fieldCtxt.propType)) && DEPRECATED; + FieldContext fieldCtxt(field, object); generateField(ctxt, fieldCtxt); @@ -671,6 +708,12 @@ generateProperty(ctxt, fieldCtxt); } + QString includes; + foreach(const QString &objectName, ctxt.parentObjects) { + includes.append("#include \"" + objectName.toLower() + ".h\"\n"); + } + + outInclude.replace("$(INCLUDE)", includes); outInclude.replace("$(ENUMS)", ctxt.enums); outInclude.replace("$(ENUMS_COUNT)", ctxt.enumsCount); outInclude.replace("$(DATAFIELDS)", ctxt.fields); diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/uavobjgenerator/uavobjectparser.cpp librepilot-16.09+r735~gaba11f0/ground/uavobjgenerator/uavobjectparser.cpp --- librepilot-16.09+r711~ga4c0bcf/ground/uavobjgenerator/uavobjectparser.cpp 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/uavobjgenerator/uavobjectparser.cpp 2018-09-29 10:02:39.000000000 +0000 @@ -69,6 +69,15 @@ return objInfo[objIndex]; } +ObjectInfo *UAVObjectParser::getObjectByName(const QString & objName) +{ + foreach(ObjectInfo * info, objInfo) { + if (objName == info->name) { + return info; + } + } + return 0; +} /** * Get the name of the object */ @@ -404,6 +413,7 @@ */ QString UAVObjectParser::processObjectFields(QDomNode & childNode, ObjectInfo *info) { + bool isClone = false; // Create field FieldInfo *field = new FieldInfo; // Get name attribute @@ -419,20 +429,39 @@ // field that has already been declared elemAttr = elemAttributes.namedItem("cloneof"); if (!elemAttr.isNull()) { - QString parentName = elemAttr.nodeValue(); + QString parentName = elemAttr.nodeValue().section('.', 1); + + ObjectInfo *parentObject; + + if (parentName.isEmpty()) { + parentName = elemAttr.nodeValue(); + parentObject = info; + } else { + QString objName = elemAttr.nodeValue().section('.', 0, 0); + parentObject = getObjectByName(objName); + if (!parentObject) { + return QString("Object:field:cloneof parent object unknown"); + } + } + if (!parentName.isEmpty()) { - foreach(FieldInfo * parent, info->fields) { + foreach(FieldInfo * parent, parentObject->fields) { if (parent->name == parentName) { // clone from this parent *field = *parent; // safe shallow copy, no ptrs in struct field->name = name; // set our name - // Add field to object - info->fields.append(field); - // Done - return QString(); + // Done, but allow certain overrides + field->parentObjectName = parentObject->name; + field->parentFieldName = parent->name; + isClone = true; + + break; } } - return QString("Object:field::cloneof parent unknown"); + + if (!isClone) { + return QString("Object:field:cloneof parent unknown"); + } } else { return QString("Object:field:cloneof attribute is empty"); } @@ -459,31 +488,45 @@ // Get units attribute elemAttr = elemAttributes.namedItem("units"); if (elemAttr.isNull()) { - return QString("Object:field:units attribute is missing"); + if (!isClone) { + return QString("Object:field:units attribute is missing"); + } + } else { + if (isClone) { + return QString("Object:field:units attribute is not allowed for cloned fields"); + } + field->units = elemAttr.nodeValue(); + all_units << field->units; } - - field->units = elemAttr.nodeValue(); - all_units << field->units; - // Get type attribute elemAttr = elemAttributes.namedItem("type"); if (elemAttr.isNull()) { - return QString("Object:field:type attribute is missing"); - } - - int index = fieldTypeStrXML.indexOf(elemAttr.nodeValue()); - if (index >= 0) { - field->type = (FieldType)index; - field->numBytes = fieldTypeNumBytes[index]; + if (!isClone) { + return QString("Object:field:type attribute is missing"); + } } else { - return QString("Object:field:type attribute value is invalid"); + if (isClone) { + return QString("Object:field:type attribute is not allowed for cloned fields"); + } + int index = fieldTypeStrXML.indexOf(elemAttr.nodeValue()); + if (index >= 0) { + field->type = (FieldType)index; + field->numBytes = fieldTypeNumBytes[index]; + } else { + return QString("Object:field:type attribute value is invalid"); + } } // Get numelements or elementnames attribute - field->numElements = 0; + if (!isClone) { + field->numElements = 0; + } // Look for element names as an attribute first elemAttr = elemAttributes.namedItem("elementnames"); if (!elemAttr.isNull()) { + if (isClone) { + return QString("Object:field:elementnames attribute is not allowed for cloned fields"); + } // Get element names QStringList names = elemAttr.nodeValue().split(",", QString::SkipEmptyParts); for (int n = 0; n < names.length(); ++n) { @@ -497,6 +540,9 @@ // Look for a list of child elementname nodes QDomNode listNode = childNode.firstChildElement("elementnames"); if (!listNode.isNull()) { + if (isClone) { + return QString("Object:field:elementnames element is not allowed for cloned fields"); + } for (QDomElement node = listNode.firstChildElement("elementname"); !node.isNull(); node = node.nextSiblingElement("elementname")) { QDomNode name = node.firstChild(); @@ -515,6 +561,9 @@ if (elemAttr.isNull()) { return QString("Object:field:elements and Object:field:elementnames attribute/element is missing"); } else { + if (isClone) { + return QString("Object:field:elements attribute is not allowed for cloned fields"); + } field->numElements = elemAttr.nodeValue().toInt(); for (int n = 0; n < field->numElements; ++n) { field->elementNames.append(QString("%1").arg(n)); @@ -524,15 +573,18 @@ } } // Get options attribute or child elements (only if an enum type) + // We allow "options" attribute/element for cloned fields also, but they work slightly different here - + // they set limits on parent options. + if (field->type == FIELDTYPE_ENUM) { // Look for options attribute + QStringList options; elemAttr = elemAttributes.namedItem("options"); if (!elemAttr.isNull()) { - QStringList options = elemAttr.nodeValue().split(",", QString::SkipEmptyParts); + options = elemAttr.nodeValue().split(",", QString::SkipEmptyParts); for (int n = 0; n < options.length(); ++n) { options[n] = options[n].trimmed(); } - field->options = options; } else { // Look for a list of child 'option' nodes QDomNode listNode = childNode.firstChildElement("options"); @@ -541,12 +593,28 @@ !node.isNull(); node = node.nextSiblingElement("option")) { QDomNode name = node.firstChild(); if (!name.isNull() && name.isText() && !name.nodeValue().isEmpty()) { - field->options.append(name.nodeValue()); + options.append(name.nodeValue()); } } } } - field->numOptions = field->options.size(); + + if (isClone) { + if (!options.isEmpty()) { + // Verify options subset and build limits value from it. + foreach(const QString &option, options) { + if (!field->options.contains(option)) { + return QString("Object:field:options is not a subset of parent options"); + } + } + field->limitValues = QString("%EQ:") + options.join(':'); + qDebug() << "Created field->limitValues: " << field->limitValues; + } + } else { + field->numOptions = options.size(); + field->options = options; + } + if (field->numOptions == 0) { return QString("Object:field:options attribute/element is missing"); } @@ -554,12 +622,7 @@ // Get the default value attribute (required for settings objects, optional for the rest) elemAttr = elemAttributes.namedItem("defaultvalue"); - if (elemAttr.isNull()) { - if (info->isSettings) { - return QString("Object:field:defaultvalue attribute is missing (required for settings objects)"); - } - field->defaultValues = QStringList(); - } else { + if (!elemAttr.isNull()) { QStringList defaults = elemAttr.nodeValue().split(",", QString::SkipEmptyParts); for (int n = 0; n < defaults.length(); ++n) { defaults[n] = defaults[n].trimmed(); @@ -578,6 +641,9 @@ } field->defaultValues = defaults; } + if (field->defaultValues.isEmpty() && info->isSettings) { + return QString("Object:field:defaultvalue attribute is missing (required for settings objects)"); + } // Limits attribute elemAttr = elemAttributes.namedItem("limits"); diff -Nru librepilot-16.09+r711~ga4c0bcf/ground/uavobjgenerator/uavobjectparser.h librepilot-16.09+r735~gaba11f0/ground/uavobjgenerator/uavobjectparser.h --- librepilot-16.09+r711~ga4c0bcf/ground/uavobjgenerator/uavobjectparser.h 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/ground/uavobjgenerator/uavobjectparser.h 2018-09-29 10:02:39.000000000 +0000 @@ -59,6 +59,8 @@ bool defaultElementNames; QStringList defaultValues; QString limitValues; + QString parentObjectName; + QString parentFieldName; } FieldInfo; /** @@ -113,6 +115,7 @@ quint32 getObjectID(int objIndex); ObjectInfo *getObjectByIndex(int objIndex); + ObjectInfo *getObjectByName(const QString & objName); int getNumBytes(int objIndex); QStringList all_units; diff -Nru librepilot-16.09+r711~ga4c0bcf/version-info.json librepilot-16.09+r735~gaba11f0/version-info.json --- librepilot-16.09+r711~ga4c0bcf/version-info.json 2018-04-29 23:01:26.000000000 +0000 +++ librepilot-16.09+r735~gaba11f0/version-info.json 2018-09-29 10:02:39.000000000 +0000 @@ -1 +1 @@ -{"origin": "https://bitbucket.org/librepilot/librepilot.git", "last_tag": "16.09", "hash": "a4c0bcfb1a68e89d923f76e001d4a23becfb3eff", "time": "1525042886", "dirty": false, "branch": "next", "num_commits_past_tag": "711"} \ No newline at end of file +{"origin": "https://bitbucket.org/librepilot/librepilot.git", "last_tag": "16.09", "hash": "aba11f0e10b4d063fdfc30d16124dcfc75793309", "time": "1538215359", "dirty": false, "branch": "next", "num_commits_past_tag": "735"} \ No newline at end of file