Organize Linux and macOS as separate applications
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
#include "../src/MainWindow.h"
|
||||
#include "../src/GlobeWidget.h"
|
||||
#include "../src/MapWidget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QAction>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMetaObject>
|
||||
#include <QSpinBox>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QTableView>
|
||||
#include <QTabWidget>
|
||||
#include <QSplitter>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
void sendLine(MainWindow &window, const QString &line)
|
||||
{
|
||||
const bool invoked = QMetaObject::invokeMethod(
|
||||
&window, "handleSerialLine", Qt::DirectConnection, Q_ARG(QString, line));
|
||||
assert(invoked);
|
||||
}
|
||||
|
||||
void flushUpdates(MainWindow &window)
|
||||
{
|
||||
const bool invoked = QMetaObject::invokeMethod(
|
||||
&window, "flushUiUpdates", Qt::DirectConnection);
|
||||
assert(invoked);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
qputenv("LOCATION_PLOTTER_PORT", "/dev/test-usb-uart");
|
||||
MainWindow window;
|
||||
qunsetenv("LOCATION_PLOTTER_PORT");
|
||||
|
||||
{
|
||||
QTemporaryDir uartLogDirectory;
|
||||
assert(uartLogDirectory.isValid());
|
||||
qputenv("LOCATION_PLOTTER_UART_LOG_DIR", uartLogDirectory.path().toUtf8());
|
||||
bool loggingStarted = false;
|
||||
assert(QMetaObject::invokeMethod(
|
||||
&window, "startUartLogging", Qt::DirectConnection,
|
||||
Q_RETURN_ARG(bool, loggingStarted),
|
||||
Q_ARG(QString, QStringLiteral("/dev/test-uart")), Q_ARG(int, 460800)));
|
||||
assert(loggingStarted);
|
||||
sendLine(window, QStringLiteral("raw uart test line"));
|
||||
assert(QMetaObject::invokeMethod(&window, "stopUartLogging", Qt::DirectConnection));
|
||||
qunsetenv("LOCATION_PLOTTER_UART_LOG_DIR");
|
||||
|
||||
const QStringList logFiles = QDir(uartLogDirectory.path()).entryList(
|
||||
{QStringLiteral("uart_*.log")}, QDir::Files);
|
||||
assert(logFiles.size() == 1);
|
||||
QFile logFile(QDir(uartLogDirectory.path()).filePath(logFiles.constFirst()));
|
||||
assert(logFile.open(QIODevice::ReadOnly | QIODevice::Text));
|
||||
const QString contents = QString::fromUtf8(logFile.readAll());
|
||||
assert(contents.contains(QStringLiteral("[SESSION START] port=/dev/test-uart baud=460800")));
|
||||
assert(contents.contains(QRegularExpression(
|
||||
QStringLiteral(R"(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\] raw uart test line)"))));
|
||||
assert(contents.contains(QStringLiteral("[SESSION END]")));
|
||||
}
|
||||
|
||||
auto *table = window.findChild<QTableView *>();
|
||||
auto *referenceLatInput = window.findChild<QLineEdit *>(QStringLiteral("referenceLatInput"));
|
||||
auto *referenceLonInput = window.findChild<QLineEdit *>(QStringLiteral("referenceLonInput"));
|
||||
auto *cepValue = window.findChild<QLabel *>(QStringLiteral("cepValue"));
|
||||
auto *maxPointsInput = window.findChild<QSpinBox *>(QStringLiteral("maxPointsInput"));
|
||||
auto *elevationMaskSpin = window.findChild<QDoubleSpinBox *>(QStringLiteral("elevationMaskSpin"));
|
||||
auto *showTelemetry = window.findChild<QCheckBox *>(QStringLiteral("showTelemetryCheck"));
|
||||
auto *showRecentPoints = window.findChild<QCheckBox *>(QStringLiteral("showRecentPointsCheck"));
|
||||
auto *showAverage = window.findChild<QCheckBox *>(QStringLiteral("showAverageCheck"));
|
||||
auto *portSelector = window.findChild<QComboBox *>(QStringLiteral("portSelector"));
|
||||
auto *refreshRinexButton =
|
||||
window.findChild<QPushButton *>(QStringLiteral("refreshRinexButton"));
|
||||
auto *map = window.findChild<MapWidget *>();
|
||||
auto *mainViewTabs = window.findChild<QTabWidget *>(QStringLiteral("mainViewTabs"));
|
||||
auto *sideSplitter = window.findChild<QSplitter *>(QStringLiteral("sideSplitter"));
|
||||
auto *rinexUpdateButton = window.findChild<QPushButton *>(QStringLiteral("rinexUpdateButton"));
|
||||
auto *openLogAction = window.findChild<QAction *>(QStringLiteral("openLogAction"));
|
||||
auto *toggleSerialAction = window.findChild<QAction *>(QStringLiteral("toggleSerialAction"));
|
||||
auto *rinexUpdateAction = window.findChild<QAction *>(QStringLiteral("rinexUpdateAction"));
|
||||
assert(table != nullptr);
|
||||
assert(referenceLatInput != nullptr);
|
||||
assert(referenceLonInput != nullptr);
|
||||
assert(cepValue != nullptr);
|
||||
assert(maxPointsInput != nullptr);
|
||||
assert(elevationMaskSpin != nullptr);
|
||||
assert(elevationMaskSpin->value() == 10);
|
||||
assert(elevationMaskSpin->minimum() == 0);
|
||||
assert(elevationMaskSpin->maximum() == 90.0);
|
||||
elevationMaskSpin->setValue(12.5);
|
||||
assert(elevationMaskSpin->value() == 12.5);
|
||||
assert(showTelemetry != nullptr);
|
||||
assert(showRecentPoints != nullptr);
|
||||
assert(showAverage != nullptr);
|
||||
assert(portSelector != nullptr);
|
||||
assert(refreshRinexButton != nullptr);
|
||||
assert(refreshRinexButton->text() == QStringLiteral("RINEX Guncelle")
|
||||
|| refreshRinexButton->text() == QStringLiteral("RINEX indiriliyor..."));
|
||||
assert(portSelector->isEditable());
|
||||
assert(portSelector->count() >= 1);
|
||||
assert(portSelector->currentText() == QStringLiteral("/dev/test-usb-uart"));
|
||||
for (int i = 0; i < portSelector->count(); ++i) {
|
||||
assert(portSelector->itemText(i).startsWith(QStringLiteral("/dev/")));
|
||||
assert(!portSelector->itemText(i).trimmed().isEmpty());
|
||||
}
|
||||
assert(map != nullptr);
|
||||
assert(mainViewTabs != nullptr);
|
||||
assert(mainViewTabs->count() == 2);
|
||||
assert(sideSplitter != nullptr);
|
||||
assert(sideSplitter->orientation() == Qt::Vertical);
|
||||
assert(rinexUpdateButton != nullptr);
|
||||
assert(!rinexUpdateButton->toolTip().isEmpty());
|
||||
assert(openLogAction != nullptr && !openLogAction->shortcut().isEmpty());
|
||||
assert(toggleSerialAction != nullptr && !toggleSerialAction->shortcut().isEmpty());
|
||||
assert(rinexUpdateAction != nullptr && !rinexUpdateAction->shortcut().isEmpty());
|
||||
|
||||
QVector<TelemetryPoint> followPoints;
|
||||
TelemetryPoint gpsPoint;
|
||||
gpsPoint.source = LocationSource::Gps;
|
||||
gpsPoint.latitude = 40.0;
|
||||
gpsPoint.longitude = 29.0;
|
||||
followPoints.push_back(gpsPoint);
|
||||
TelemetryPoint correctedPoint = gpsPoint;
|
||||
correctedPoint.source = LocationSource::ImuCorrected;
|
||||
correctedPoint.latitude = 40.25;
|
||||
correctedPoint.longitude = 29.5;
|
||||
followPoints.push_back(correctedPoint);
|
||||
TelemetryPoint trajectoryPoint = gpsPoint;
|
||||
trajectoryPoint.source = LocationSource::Trajectory;
|
||||
trajectoryPoint.latitude = 41.0;
|
||||
trajectoryPoint.longitude = 30.0;
|
||||
followPoints.push_back(trajectoryPoint);
|
||||
map->setPoints(followPoints);
|
||||
assert(std::abs(map->centerCoordinate().x() - 40.25) < 0.000001);
|
||||
assert(std::abs(map->centerCoordinate().y() - 29.5) < 0.000001);
|
||||
map->clearPoints();
|
||||
|
||||
sendLine(window, QStringLiteral("[TRK] tracked satellites=2 prn_indices=[2,7]"));
|
||||
assert(map != nullptr);
|
||||
auto *globe = window.findChild<GlobeWidget *>();
|
||||
assert(globe != nullptr);
|
||||
assert(globe->observedPrns()
|
||||
== QSet<QString>({QStringLiteral("G03"), QStringLiteral("G08")}));
|
||||
sendLine(window, QStringLiteral("[TRK] tracked satellites=1 prn_indices=[13]"));
|
||||
assert(globe->observedPrns() == QSet<QString>({QStringLiteral("G14")}));
|
||||
|
||||
assert(QMetaObject::invokeMethod(map, "referenceLocationSelected", Qt::DirectConnection,
|
||||
Q_ARG(double, 40.25), Q_ARG(double, 29.5)));
|
||||
assert(referenceLatInput->text() == QStringLiteral("40.250000000000000"));
|
||||
assert(referenceLonInput->text() == QStringLiteral("29.500000000000000"));
|
||||
referenceLatInput->clear();
|
||||
referenceLonInput->clear();
|
||||
assert(QMetaObject::invokeMethod(&window, "updateReferenceLocation", Qt::DirectConnection));
|
||||
showTelemetry->setChecked(false);
|
||||
assert(!showRecentPoints->isEnabled());
|
||||
assert(!showAverage->isEnabled());
|
||||
showTelemetry->setChecked(true);
|
||||
assert(showRecentPoints->isEnabled());
|
||||
assert(showAverage->isEnabled());
|
||||
|
||||
sendLine(window, QStringLiteral("lattitude : 40.78925034214206846173"));
|
||||
sendLine(window, QStringLiteral("longitude : 29.46277530841727809729"));
|
||||
assert(table->model()->rowCount() == 0);
|
||||
|
||||
sendLine(window, QStringLiteral("NAV solve done"));
|
||||
flushUpdates(window);
|
||||
assert(table->model()->rowCount() == 1);
|
||||
assert(table->model()->data(table->model()->index(0, 2)).toString()
|
||||
== QStringLiteral("40.78925034214206846173"));
|
||||
assert(table->model()->data(table->model()->index(0, 3)).toString()
|
||||
== QStringLiteral("29.46277530841727809729"));
|
||||
|
||||
sendLine(window, QStringLiteral(
|
||||
"[LOCATION] source=gps lat=40.1 lon=29.1 height=100 gdop=2 pdop=1.5 "
|
||||
"hdop=1 vdop=1.2 tdop=0.8 tracked=5 used=5"));
|
||||
sendLine(window, QStringLiteral(
|
||||
"[LOCATION] source=imu_corrected lat=40.10001 lon=29.10001 height=100.2 "
|
||||
"gdop=2 pdop=1.5 hdop=1 vdop=1.2 tdop=0.8 tracked=5 used=5"));
|
||||
flushUpdates(window);
|
||||
assert(table->model()->rowCount() == 3);
|
||||
assert(table->model()->data(table->model()->index(1, 1)).toString()
|
||||
== QStringLiteral("GPS"));
|
||||
assert(table->model()->data(table->model()->index(2, 1)).toString()
|
||||
== QStringLiteral("IMU corrected"));
|
||||
sendLine(window, QStringLiteral(
|
||||
"[LOCATION] source=trajectory lat=40.10002 lon=29.10002 height=100 tracked=4 used=4"));
|
||||
flushUpdates(window);
|
||||
assert(table->model()->rowCount() == 4);
|
||||
assert(table->model()->data(table->model()->index(3, 1)).toString()
|
||||
== QStringLiteral("Trajectory (truth)"));
|
||||
|
||||
QTemporaryFile replayLog;
|
||||
assert(replayLog.open());
|
||||
replayLog.write(
|
||||
"[LOCATION] source=trajectory lat=40.0 lon=29.0 height=100 tracked=4 used=4\n"
|
||||
"[LOCATION] source=gps lat=40.0001 lon=29.0001 height=104 tracked=4 used=4\n"
|
||||
"[LOCATION] source=imu_corrected lat=40.00002 lon=29.00002 height=101 tracked=4 used=4\n");
|
||||
replayLog.flush();
|
||||
assert(window.loadTelemetryLog(replayLog.fileName()));
|
||||
assert(table->model()->rowCount() == 3);
|
||||
assert(table->model()->data(table->model()->index(0, 1)).toString()
|
||||
== QStringLiteral("Trajectory (truth)"));
|
||||
|
||||
QTemporaryFile splitReplayLog;
|
||||
assert(splitReplayLog.open());
|
||||
splitReplayLog.write("latitude: 40.25\nlongitude: 29.50\n");
|
||||
splitReplayLog.flush();
|
||||
assert(window.loadTelemetryLog(splitReplayLog.fileName()));
|
||||
assert(table->model()->rowCount() == 1);
|
||||
|
||||
// A partial fix from an earlier solve must not be completed using a field
|
||||
// from the following solve.
|
||||
assert(QMetaObject::invokeMethod(&window, "clearPoints", Qt::DirectConnection));
|
||||
sendLine(window, QStringLiteral("NAV solve start"));
|
||||
sendLine(window, QStringLiteral("latitude: 40.0"));
|
||||
sendLine(window, QStringLiteral("NAV solve start"));
|
||||
sendLine(window, QStringLiteral("longitude: 29.0"));
|
||||
sendLine(window, QStringLiteral("NAV solve done"));
|
||||
flushUpdates(window);
|
||||
assert(table->model()->rowCount() == 0);
|
||||
|
||||
assert(QMetaObject::invokeMethod(&window, "clearPoints", Qt::DirectConnection));
|
||||
sendLine(window, QStringLiteral(
|
||||
"lat=40.78925034214206846173 long=29.46277530841727809729"));
|
||||
flushUpdates(window);
|
||||
assert(table->model()->rowCount() == 1);
|
||||
|
||||
referenceLatInput->setText(QStringLiteral("40.78925034214206846173"));
|
||||
referenceLonInput->setText(QStringLiteral("29.46277530841727809729"));
|
||||
const bool referenceUpdated = QMetaObject::invokeMethod(
|
||||
&window, "updateReferenceLocation", Qt::DirectConnection);
|
||||
assert(referenceUpdated);
|
||||
assert(cepValue->text().startsWith(QStringLiteral("0.00 m")));
|
||||
assert(cepValue->text().contains(QStringLiteral("N=1")));
|
||||
|
||||
const bool cleared = QMetaObject::invokeMethod(&window, "clearPoints", Qt::DirectConnection);
|
||||
assert(cleared);
|
||||
assert(table->model()->rowCount() == 0);
|
||||
assert(referenceLatInput->text() == QStringLiteral("40.78925034214206846173"));
|
||||
assert(referenceLonInput->text() == QStringLiteral("29.46277530841727809729"));
|
||||
assert(cepValue->text() == QStringLiteral("-"));
|
||||
|
||||
referenceLatInput->setText(QStringLiteral("40.0"));
|
||||
referenceLonInput->setText(QStringLiteral("29.0"));
|
||||
assert(QMetaObject::invokeMethod(&window, "updateReferenceLocation", Qt::DirectConnection));
|
||||
sendLine(window, QStringLiteral("lat=40.000 long=29.000"));
|
||||
sendLine(window, QStringLiteral("lat=40.001 long=29.000"));
|
||||
sendLine(window, QStringLiteral("lat=40.002 long=29.000"));
|
||||
flushUpdates(window);
|
||||
assert(cepValue->text().startsWith(QStringLiteral("111 m")));
|
||||
assert(cepValue->text().contains(QStringLiteral("N=3")));
|
||||
assert(QMetaObject::invokeMethod(&window, "clearPoints", Qt::DirectConnection));
|
||||
|
||||
referenceLatInput->setText(QStringLiteral("40.78925034214206846173"));
|
||||
referenceLonInput->setText(QStringLiteral("29.46277530841727809729"));
|
||||
assert(QMetaObject::invokeMethod(&window, "updateReferenceLocation", Qt::DirectConnection));
|
||||
|
||||
maxPointsInput->setValue(10000);
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
sendLine(window, QStringLiteral("lat=40.78925 long=29.46277 sats=7"));
|
||||
}
|
||||
flushUpdates(window);
|
||||
assert(table->model()->rowCount() == 10000);
|
||||
assert(cepValue->text().contains(QStringLiteral("N=10000")));
|
||||
assert(QMetaObject::invokeMethod(&window, "clearPoints", Qt::DirectConnection));
|
||||
maxPointsInput->setValue(10);
|
||||
|
||||
{
|
||||
MapWidget followMap;
|
||||
TelemetryPoint corrected;
|
||||
corrected.source = LocationSource::ImuCorrected;
|
||||
corrected.latitude = 40.1;
|
||||
corrected.longitude = 29.1;
|
||||
corrected.receivedAt = QDateTime::currentDateTimeUtc();
|
||||
|
||||
TelemetryPoint rawGps;
|
||||
rawGps.source = LocationSource::Gps;
|
||||
rawGps.latitude = 41.0;
|
||||
rawGps.longitude = 30.0;
|
||||
rawGps.receivedAt = corrected.receivedAt.addMSecs(100);
|
||||
followMap.setPoints({corrected, rawGps});
|
||||
assert(followMap.centerCoordinate() == QPointF(40.1, 29.1));
|
||||
|
||||
rawGps.receivedAt = corrected.receivedAt.addMSecs(1100);
|
||||
followMap.setPoints({corrected, rawGps});
|
||||
assert(followMap.centerCoordinate() == QPointF(41.0, 30.0));
|
||||
}
|
||||
|
||||
const QByteArray screenshotPath = qgetenv("LOCATION_PLOTTER_TEST_SCREENSHOT");
|
||||
if (!screenshotPath.isEmpty()) {
|
||||
sendLine(window, QStringLiteral("NAV solve start sats:5"));
|
||||
sendLine(window, QStringLiteral("tracking PRNs: G03, G08, G14, G21, G27"));
|
||||
sendLine(window, QStringLiteral(
|
||||
"lat=40.78925034214206846173 long=29.46277530841727809729 "
|
||||
"hheight=-89.05779508221894502640 sats=7 PDOP=1.235"));
|
||||
flushUpdates(window);
|
||||
mainViewTabs->setCurrentIndex(1);
|
||||
window.show();
|
||||
app.processEvents();
|
||||
window.repaint();
|
||||
app.processEvents();
|
||||
assert(window.grab().save(QString::fromLocal8Bit(screenshotPath), "PNG"));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "../src/MapWidget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QElapsedTimer>
|
||||
#include <QHostAddress>
|
||||
#include <QSet>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QThread>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QTcpServer tileServer;
|
||||
assert(tileServer.listen(QHostAddress::LocalHost, 0));
|
||||
|
||||
int requestCount = 0;
|
||||
QSet<QByteArray> requestedPaths;
|
||||
QObject::connect(&tileServer, &QTcpServer::newConnection, [&]() {
|
||||
while (tileServer.hasPendingConnections()) {
|
||||
QTcpSocket *socket = tileServer.nextPendingConnection();
|
||||
const auto respond = [&, socket]() {
|
||||
if (!socket->canReadLine()) {
|
||||
return;
|
||||
}
|
||||
const QByteArray requestLine = socket->readLine().trimmed();
|
||||
const QList<QByteArray> fields = requestLine.split(' ');
|
||||
if (fields.size() >= 2) {
|
||||
++requestCount;
|
||||
requestedPaths.insert(fields[1]);
|
||||
}
|
||||
socket->readAll();
|
||||
socket->write("HTTP/1.1 503 Service Unavailable\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Connection: close\r\n\r\n");
|
||||
socket->disconnectFromHost();
|
||||
};
|
||||
QObject::connect(socket, &QTcpSocket::readyRead, socket, respond);
|
||||
QObject::connect(socket, &QTcpSocket::disconnected,
|
||||
socket, &QObject::deleteLater);
|
||||
respond();
|
||||
}
|
||||
});
|
||||
|
||||
const QByteArray tileUrl = QByteArray("http://127.0.0.1:")
|
||||
+ QByteArray::number(tileServer.serverPort()) + "/{z}/{x}/{y}.png";
|
||||
qputenv("LOCATION_PLOTTER_TILE_URL_TEMPLATE", tileUrl);
|
||||
|
||||
{
|
||||
MapWidget map;
|
||||
map.resize(640, 480);
|
||||
map.show();
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
while (timer.elapsed() < 1200) {
|
||||
app.processEvents(QEventLoop::AllEvents, 50);
|
||||
QThread::msleep(5);
|
||||
}
|
||||
|
||||
// Every URL fails. Seeing any URL more than once proves that a failed
|
||||
// tile schedules a new paint/request instead of remaining a blank
|
||||
// placeholder forever.
|
||||
assert(!requestedPaths.isEmpty());
|
||||
assert(requestCount > requestedPaths.size());
|
||||
}
|
||||
|
||||
qunsetenv("LOCATION_PLOTTER_TILE_URL_TEMPLATE");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
#include "../src/LocationParser.h"
|
||||
#include "../src/RinexNavigation.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
bool closeTo(double left, double right, double tolerance = 0.000001)
|
||||
{
|
||||
return std::fabs(left - right) < tolerance;
|
||||
}
|
||||
|
||||
QString rinexField(double value)
|
||||
{
|
||||
return QStringLiteral("%1").arg(value, 19, 'E', 12);
|
||||
}
|
||||
|
||||
QString rinexContinuation(std::initializer_list<double> values)
|
||||
{
|
||||
QString line(4, QLatin1Char(' '));
|
||||
for (double value : values) line += rinexField(value);
|
||||
return line + QLatin1Char('\n');
|
||||
}
|
||||
|
||||
QByteArray syntheticGpsNavigation(const QDateTime &gpsEpochLabel, int gpsWeek, double toe)
|
||||
{
|
||||
QString rinex = QStringLiteral(
|
||||
" 3.05 N: GNSS NAV DATA M: MIXED RINEX VERSION / TYPE\n"
|
||||
" END OF HEADER\n");
|
||||
rinex += QStringLiteral("G01 %1").arg(
|
||||
gpsEpochLabel.toString(QStringLiteral("yyyy MM dd HH mm ss")));
|
||||
rinex += rinexField(0.0) + rinexField(0.0) + rinexField(0.0) + QLatin1Char('\n');
|
||||
rinex += rinexContinuation({1.0, 0.0, 0.0, 0.0});
|
||||
rinex += rinexContinuation({0.0, 0.0, 0.0, 5153.7954775});
|
||||
rinex += rinexContinuation({toe, 0.0, 0.0, 0.0});
|
||||
rinex += rinexContinuation({0.0, 0.0, 0.0, 0.0});
|
||||
rinex += rinexContinuation({0.0, 0.0, static_cast<double>(gpsWeek), 0.0});
|
||||
rinex += rinexContinuation({2.0, 0.0, 0.0, 1.0});
|
||||
rinex += rinexContinuation({0.0, 0.0, 0.0, 0.0});
|
||||
return rinex.toLatin1();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(
|
||||
QStringLiteral("DBG lat=41.0082000 long=28.9784000 hheight=123.45 sats=7"));
|
||||
assert(parsed.has_value());
|
||||
assert(parsed->hasLocation());
|
||||
assert(closeTo(*parsed->latitude, 41.0082));
|
||||
assert(closeTo(*parsed->longitude, 28.9784));
|
||||
assert(closeTo(*parsed->hheight, 123.45));
|
||||
assert(parsed->satellites == 7);
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(
|
||||
QStringLiteral("latitude: -12,5 longitude: 33,25 height: 99 activeSats: 4"));
|
||||
assert(parsed.has_value());
|
||||
assert(parsed->hasLocation());
|
||||
assert(closeTo(*parsed->latitude, -12.5));
|
||||
assert(closeTo(*parsed->longitude, 33.25));
|
||||
assert(parsed->latitudeText == QStringLiteral("-12.5"));
|
||||
assert(parsed->longitudeText == QStringLiteral("33.25"));
|
||||
assert(closeTo(*parsed->hheight, 99.0));
|
||||
assert(parsed->satellites == 4);
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(QStringLiteral("satellites_active=11"));
|
||||
assert(parsed.has_value());
|
||||
assert(!parsed->hasLocation());
|
||||
assert(parsed->satellites == 11);
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(
|
||||
QStringLiteral("lat=200 long=28 hheight=100 active_sats=8"));
|
||||
assert(parsed.has_value());
|
||||
assert(!parsed->hasLocation());
|
||||
assert(parsed->latitudeText.isEmpty());
|
||||
assert(parsed->longitudeText.isEmpty());
|
||||
assert(!parsed->hheight.has_value());
|
||||
assert(parsed->satellites == 8);
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(
|
||||
QStringLiteral("lattitude : 40.78925034214206846173"));
|
||||
assert(parsed.has_value());
|
||||
assert(!parsed->hasLocation());
|
||||
assert(closeTo(*parsed->latitude, 40.78925034214206846173));
|
||||
assert(parsed->latitudeText == QStringLiteral("40.78925034214206846173"));
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(
|
||||
QStringLiteral("longitude : 29.46277530841727809729"));
|
||||
assert(parsed.has_value());
|
||||
assert(!parsed->hasLocation());
|
||||
assert(closeTo(*parsed->longitude, 29.46277530841727809729));
|
||||
assert(parsed->longitudeText == QStringLiteral("29.46277530841727809729"));
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(
|
||||
QStringLiteral("height : -89.05779508221894502640 "));
|
||||
assert(parsed.has_value());
|
||||
assert(!parsed->hasLocation());
|
||||
assert(closeTo(*parsed->hheight, -89.05779508221894502640));
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(
|
||||
QStringLiteral("NAV solve start sats:4 mode:periodic abs_span_ms:2.176"));
|
||||
assert(parsed.has_value());
|
||||
assert(!parsed->hasLocation());
|
||||
assert(parsed->satellites == 4);
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(
|
||||
QStringLiteral("[NAV] navigation software running with 5 satellites"));
|
||||
assert(parsed.has_value());
|
||||
assert(!parsed->hasLocation());
|
||||
assert(parsed->satellites == 5);
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(QStringLiteral("GDOP : 3.9576744550"));
|
||||
assert(parsed.has_value());
|
||||
assert(!parsed->hasLocation());
|
||||
assert(closeTo(*parsed->dop.gdop, 3.9576744550));
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(
|
||||
QStringLiteral("PDOP : 3.2540598660 HDOP : 2.3631161789 VDOP : 2.2370935468 TDOP : 2.2526165852"));
|
||||
assert(parsed.has_value());
|
||||
assert(!parsed->hasLocation());
|
||||
assert(closeTo(*parsed->dop.pdop, 3.2540598660));
|
||||
assert(closeTo(*parsed->dop.hdop, 2.3631161789));
|
||||
assert(closeTo(*parsed->dop.vdop, 2.2370935468));
|
||||
assert(closeTo(*parsed->dop.tdop, 2.2526165852));
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(QStringLiteral("plain debug noise"));
|
||||
assert(!parsed.has_value());
|
||||
}
|
||||
|
||||
{
|
||||
const QSet<QString> prns = LocationParser::parseGpsPrns(
|
||||
QStringLiteral("tracking PRNs: 3, G08, 14; SV=G21 SVID: 27"));
|
||||
assert(prns.contains(QStringLiteral("G03")));
|
||||
assert(prns.contains(QStringLiteral("G08")));
|
||||
assert(prns.contains(QStringLiteral("G14")));
|
||||
assert(prns.contains(QStringLiteral("G21")));
|
||||
assert(prns.contains(QStringLiteral("G27")));
|
||||
assert(prns.size() == 5);
|
||||
}
|
||||
|
||||
{
|
||||
const QString line = QStringLiteral(
|
||||
"[OK] [TRK] tracked satellites=5 svn=[2,3,5,8,30]");
|
||||
const auto parsed = LocationParser::parseLine(line);
|
||||
assert(parsed.has_value());
|
||||
assert(parsed->satellites == 5);
|
||||
const QSet<QString> prns = LocationParser::parseGpsPrns(line);
|
||||
assert(prns == QSet<QString>({QStringLiteral("G03"), QStringLiteral("G04"),
|
||||
QStringLiteral("G06"), QStringLiteral("G09"),
|
||||
QStringLiteral("G31")}));
|
||||
}
|
||||
|
||||
{
|
||||
const QSet<QString> prns = LocationParser::parseGpsPrns(
|
||||
QStringLiteral("[TRK] tracked satellites=2 svn=[0,31]"));
|
||||
assert(prns == QSet<QString>({QStringLiteral("G01"), QStringLiteral("G32")}));
|
||||
}
|
||||
|
||||
{
|
||||
const QSet<QString> prns = LocationParser::parseGpsPrns(
|
||||
QStringLiteral("[TRK] tracked satellites=3 prn_indices=[0,7,31]"));
|
||||
assert(prns == QSet<QString>({QStringLiteral("G01"), QStringLiteral("G08"),
|
||||
QStringLiteral("G32")}));
|
||||
}
|
||||
|
||||
{
|
||||
const QSet<QString> prns = LocationParser::parseGpsPrns(
|
||||
QStringLiteral("[TRK] tracked satellites=2 prns=[0,7]"));
|
||||
assert(prns == QSet<QString>({QStringLiteral("G01"), QStringLiteral("G08")}));
|
||||
}
|
||||
|
||||
{
|
||||
const QSet<QString> prns = LocationParser::parseGpsPrns(
|
||||
QStringLiteral("[TRK] tracked satellites=2 prn_indices=[0,G08]"));
|
||||
assert(prns == QSet<QString>({QStringLiteral("G01"), QStringLiteral("G08")}));
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(QStringLiteral(
|
||||
"[OK] [NAV] DOP GDOP=7.0907891622 PDOP=5.6333862175 "
|
||||
"HDOP=2.9819528958 VDOP=4.7794348204 TDOP=4.3063035967"));
|
||||
assert(parsed.has_value());
|
||||
assert(closeTo(*parsed->dop.gdop, 7.0907891622));
|
||||
assert(closeTo(*parsed->dop.pdop, 5.6333862175));
|
||||
assert(closeTo(*parsed->dop.hdop, 2.9819528958));
|
||||
assert(closeTo(*parsed->dop.vdop, 4.7794348204));
|
||||
assert(closeTo(*parsed->dop.tdop, 4.3063035967));
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(QStringLiteral(
|
||||
"[OK] [NAV] position lat=40.77831900791127850425 "
|
||||
"lon=29.43579105837637044374 height=198.80186179187148809433"));
|
||||
assert(parsed.has_value());
|
||||
assert(parsed->hasLocation());
|
||||
assert(closeTo(*parsed->latitude, 40.77831900791127850425));
|
||||
assert(closeTo(*parsed->longitude, 29.43579105837637044374));
|
||||
assert(closeTo(*parsed->hheight, 198.80186179187148809433));
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(QStringLiteral(
|
||||
"[LOCATION] source=imu_corrected mode=dead_reckoning "
|
||||
"lat=40.7356 lon=31.5985 height=812.7 gps_age_ms=3180 "
|
||||
"gdop=4.7 pdop=4.1 hdop=2.7 vdop=3.0 tdop=2.3 tracked=6 used=5"));
|
||||
assert(parsed.has_value());
|
||||
assert(parsed->hasLocation());
|
||||
assert(parsed->source == LocationSource::ImuCorrected);
|
||||
assert(parsed->fusionMode == FusionMode::DeadReckoning);
|
||||
assert(parsed->gpsAgeMs == 3180);
|
||||
assert(parsed->satellites == 6);
|
||||
assert(closeTo(*parsed->dop.hdop, 2.7));
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(QStringLiteral(
|
||||
"[LOCATION] source=gps lat=40.7357 lon=31.5986 height=811 tracked=5 used=5"));
|
||||
assert(parsed.has_value());
|
||||
assert(parsed->source == LocationSource::Gps);
|
||||
assert(parsed->satellites == 5);
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(QStringLiteral(
|
||||
"[FUSION] state=moving lat=40.7357 lon=31.5986 height=811"));
|
||||
assert(parsed.has_value());
|
||||
assert(parsed->hasLocation());
|
||||
assert(parsed->source == LocationSource::ImuCorrected);
|
||||
}
|
||||
|
||||
{
|
||||
const auto parsed = LocationParser::parseLine(QStringLiteral(
|
||||
"[LOCATION] source=trajectory lat=40.7358 lon=31.5987 height=810 tracked=4 used=4"));
|
||||
assert(parsed.has_value());
|
||||
assert(parsed->hasLocation());
|
||||
assert(parsed->source == LocationSource::Trajectory);
|
||||
assert(parsed->satellites == 4);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr int gpsWeek = 2428;
|
||||
constexpr double toe = 403200.0;
|
||||
const QDateTime gpsEpoch(QDate(1980, 1, 6), QTime(0, 0), Qt::UTC);
|
||||
const QDateTime gpsLabel = gpsEpoch.addSecs(
|
||||
static_cast<qint64>(gpsWeek * 604800.0 + toe));
|
||||
const QDateTime matchingUtc = gpsLabel.addSecs(-18);
|
||||
QString error;
|
||||
const auto satellites = RinexNavigation::parseGpsNavigation(
|
||||
syntheticGpsNavigation(gpsLabel, gpsWeek, toe), matchingUtc, &error);
|
||||
assert(error.isEmpty());
|
||||
assert(satellites.size() == 1);
|
||||
const double semiMajorAxis = 5153.7954775 * 5153.7954775;
|
||||
const double node = -7.2921151467e-5 * toe;
|
||||
assert(closeTo(satellites[0].x, semiMajorAxis * std::cos(node), 0.1));
|
||||
assert(closeTo(satellites[0].y, semiMajorAxis * std::sin(node), 0.1));
|
||||
assert(closeTo(satellites[0].z, 0.0, 0.1));
|
||||
|
||||
// A local wall-clock representation must not move the satellites.
|
||||
// 11:48 at UTC+03 and 08:48 UTC represent the same physical instant.
|
||||
const auto localTimeSatellites = RinexNavigation::parseGpsNavigation(
|
||||
syntheticGpsNavigation(gpsLabel, gpsWeek, toe),
|
||||
matchingUtc.toOffsetFromUtc(3 * 60 * 60), &error);
|
||||
assert(error.isEmpty());
|
||||
assert(localTimeSatellites.size() == satellites.size());
|
||||
assert(closeTo(localTimeSatellites[0].x, satellites[0].x, 0.001));
|
||||
assert(closeTo(localTimeSatellites[0].y, satellites[0].y, 0.001));
|
||||
assert(closeTo(localTimeSatellites[0].z, satellites[0].z, 0.001));
|
||||
}
|
||||
|
||||
const QString rinexPath = qEnvironmentVariable("LOCATION_PLOTTER_TEST_RINEX");
|
||||
if (!rinexPath.isEmpty()) {
|
||||
QFile file(rinexPath);
|
||||
assert(file.open(QIODevice::ReadOnly));
|
||||
QString error;
|
||||
const auto satellites = RinexNavigation::parseGpsNavigation(
|
||||
file.readAll(), QDateTime::currentDateTimeUtc(), &error);
|
||||
assert(satellites.size() >= 20);
|
||||
for (const auto &satellite : satellites) {
|
||||
assert(satellite.prn.startsWith(QStringLiteral("G")));
|
||||
assert(std::isfinite(satellite.x));
|
||||
assert(std::isfinite(satellite.y));
|
||||
assert(std::isfinite(satellite.z));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "../src/UartReplay.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QElapsedTimer>
|
||||
#include <QEventLoop>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTimer>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
QTemporaryFile log;
|
||||
assert(log.open());
|
||||
log.write(
|
||||
"[2026-08-12 10:00:00.000] [SESSION START] port=/dev/test baud=460800\n"
|
||||
"[2026-08-12 10:00:00.001] [LOCATION] source=gps lat=40.0 lon=29.0\n"
|
||||
"diagnostic line without timestamp\n"
|
||||
"[LOCATION] source=imu_corrected lat=40.1 lon=29.1\n"
|
||||
"[FUSION] lat=40.2 lon=29.2 height=10\n"
|
||||
"[LOCATION] source=gps lat=40.3 lon=29.3\n"
|
||||
"[2026-08-12 10:00:00.999] [SESSION END]\n");
|
||||
log.flush();
|
||||
|
||||
UartReplay replay;
|
||||
QStringList received;
|
||||
QVector<qint64> receivedAt;
|
||||
QElapsedTimer elapsed;
|
||||
QEventLoop loop;
|
||||
QObject::connect(&replay, &UartReplay::lineReady, &loop, [&](const QString &line) {
|
||||
received.push_back(line);
|
||||
receivedAt.push_back(elapsed.elapsed());
|
||||
});
|
||||
QObject::connect(&replay, &UartReplay::replayFinished, &loop, &QEventLoop::quit);
|
||||
QTimer::singleShot(2000, &loop, &QEventLoop::quit);
|
||||
|
||||
elapsed.start();
|
||||
assert(replay.start(log.fileName(), 20, 10));
|
||||
assert(replay.locationCount() == 4);
|
||||
loop.exec();
|
||||
|
||||
assert(!replay.isRunning());
|
||||
assert(received.size() == 5);
|
||||
assert(received.first().startsWith(QStringLiteral("[LOCATION] source=gps")));
|
||||
assert(received.at(1) == QStringLiteral("diagnostic line without timestamp"));
|
||||
assert(received.at(3).startsWith(QStringLiteral("[FUSION]")));
|
||||
assert(receivedAt.last() >= 15);
|
||||
|
||||
QTemporaryFile splitLog;
|
||||
assert(splitLog.open());
|
||||
splitLog.write("latitude: 40.25\nlongitude: 29.50\n");
|
||||
splitLog.flush();
|
||||
received.clear();
|
||||
receivedAt.clear();
|
||||
elapsed.restart();
|
||||
assert(replay.start(splitLog.fileName(), 1, 1));
|
||||
assert(replay.locationCount() == 1);
|
||||
loop.exec();
|
||||
assert(received.size() == 2);
|
||||
|
||||
QTemporaryFile invalidLog;
|
||||
assert(invalidLog.open());
|
||||
invalidLog.write("only diagnostics\n");
|
||||
invalidLog.flush();
|
||||
assert(!replay.start(invalidLog.fileName(), 200, 50));
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user