Add test-data logger
This commit is contained in:
parent
e76787fa62
commit
e8bf4220a8
4 changed files with 264 additions and 15 deletions
|
@ -6,6 +6,7 @@ set(TESTSUITE_SOURCES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/scene_graph.cxx
|
${CMAKE_CURRENT_SOURCE_DIR}/scene_graph.cxx
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/TestPilot.cxx
|
${CMAKE_CURRENT_SOURCE_DIR}/TestPilot.cxx
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/NasalUnitTesting_TestSuite.cxx
|
${CMAKE_CURRENT_SOURCE_DIR}/NasalUnitTesting_TestSuite.cxx
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/TestDataLogger.cxx
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,5 +18,6 @@ set(TESTSUITE_HEADERS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/PrivateAccessorFDM.hxx
|
${CMAKE_CURRENT_SOURCE_DIR}/PrivateAccessorFDM.hxx
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/scene_graph.hxx
|
${CMAKE_CURRENT_SOURCE_DIR}/scene_graph.hxx
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/TestPilot.hxx
|
${CMAKE_CURRENT_SOURCE_DIR}/TestPilot.hxx
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/TestDataLogger.hxx
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|
170
test_suite/FGTestApi/TestDataLogger.cxx
Normal file
170
test_suite/FGTestApi/TestDataLogger.cxx
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 James Turner
|
||||||
|
*
|
||||||
|
* This file is part of the program FlightGear.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TestDataLogger.hxx"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <simgear/io/iostreams/sgstream.hxx>
|
||||||
|
|
||||||
|
#include "Main/globals.hxx"
|
||||||
|
|
||||||
|
namespace FGTestApi {
|
||||||
|
|
||||||
|
using DoubleVec = std::vector<double>;
|
||||||
|
|
||||||
|
static std::unique_ptr<DataLogger> static_instance;
|
||||||
|
|
||||||
|
class DataLogger::DataLoggerPrivate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
sg_ofstream _stream;
|
||||||
|
|
||||||
|
struct SampleInfo {
|
||||||
|
int column;
|
||||||
|
std::string name;
|
||||||
|
// range / units info, later
|
||||||
|
SGPropertyNode_ptr property;
|
||||||
|
};
|
||||||
|
|
||||||
|
double _currentTimeBase;
|
||||||
|
std::vector<SampleInfo> _samples;
|
||||||
|
DoubleVec _openRow;
|
||||||
|
bool _didHeader = false;
|
||||||
|
|
||||||
|
void writeCurrentRow()
|
||||||
|
{
|
||||||
|
if (!_didHeader) {
|
||||||
|
writeHeader();
|
||||||
|
_didHeader = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// capture property values into the open row data
|
||||||
|
std::for_each(_samples.begin(), _samples.end(), [this](const SampleInfo& info) {
|
||||||
|
if (info.property) {
|
||||||
|
_openRow[info.column] = info.property->getDoubleValue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// write time base
|
||||||
|
_stream << globals->get_sim_time_sec() << ",";
|
||||||
|
|
||||||
|
for (const auto v : _openRow) {
|
||||||
|
if (std::isnan(v)) {
|
||||||
|
_stream << ","; // skip this data point
|
||||||
|
} else {
|
||||||
|
_stream << v << ",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_stream << "\n";
|
||||||
|
|
||||||
|
std::fill(_openRow.begin(), _openRow.end(), std::numeric_limits<double>::quiet_NaN());
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeHeader()
|
||||||
|
{
|
||||||
|
_stream << "sim-time, ";
|
||||||
|
std::for_each(_samples.begin(), _samples.end(), [this](const SampleInfo& info) {
|
||||||
|
_stream << info.name << ", ";
|
||||||
|
});
|
||||||
|
|
||||||
|
_stream << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DataLogger::DataLogger()
|
||||||
|
{
|
||||||
|
d.reset(new DataLoggerPrivate);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataLogger::~DataLogger()
|
||||||
|
{
|
||||||
|
d->_stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DataLogger::isActive()
|
||||||
|
{
|
||||||
|
return static_instance != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataLogger* DataLogger::instance()
|
||||||
|
{
|
||||||
|
if (!static_instance) {
|
||||||
|
static_instance.reset(new DataLogger);
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_instance.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataLogger::initTest(const std::string& testName)
|
||||||
|
{
|
||||||
|
d->_stream = sg_ofstream(testName + "_trace.csv");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataLogger::tearDown()
|
||||||
|
{
|
||||||
|
if (static_instance) {
|
||||||
|
static_instance.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataLogger::writeRecord()
|
||||||
|
{
|
||||||
|
d->writeCurrentRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataLogger::recordProperty(const std::string& name, SGPropertyNode_ptr prop)
|
||||||
|
{
|
||||||
|
int index = static_cast<int>(d->_samples.size());
|
||||||
|
DataLoggerPrivate::SampleInfo info{index, name, prop};
|
||||||
|
d->_samples.push_back(info);
|
||||||
|
|
||||||
|
if (d->_openRow.size() <= index) {
|
||||||
|
d->_openRow.resize(index + 1, std::numeric_limits<double>::quiet_NaN());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataLogger::recordSamplePoint(const std::string& name, double value)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(d->_samples.begin(), d->_samples.end(), [&name](const DataLoggerPrivate::SampleInfo& sample) {
|
||||||
|
return name == sample.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
if (it == d->_samples.end()) {
|
||||||
|
index = static_cast<int>(d->_samples.size());
|
||||||
|
DataLoggerPrivate::SampleInfo info{index, name};
|
||||||
|
d->_samples.push_back(info);
|
||||||
|
} else {
|
||||||
|
index = it->column;
|
||||||
|
}
|
||||||
|
|
||||||
|
// grow _openRow as required
|
||||||
|
if (d->_openRow.size() <= index) {
|
||||||
|
d->_openRow.resize(index + 1, std::numeric_limits<double>::quiet_NaN());
|
||||||
|
}
|
||||||
|
|
||||||
|
d->_openRow[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FGTestApi
|
57
test_suite/FGTestApi/TestDataLogger.hxx
Normal file
57
test_suite/FGTestApi/TestDataLogger.hxx
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 James Turner
|
||||||
|
*
|
||||||
|
* This file is part of the program FlightGear.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <simgear/props/props.hxx>
|
||||||
|
|
||||||
|
namespace FGTestApi {
|
||||||
|
|
||||||
|
class DataLogger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~DataLogger();
|
||||||
|
|
||||||
|
static DataLogger* instance();
|
||||||
|
static bool isActive();
|
||||||
|
|
||||||
|
void initTest(const std::string& testName);
|
||||||
|
|
||||||
|
void recordProperty(const std::string& name, SGPropertyNode_ptr prop);
|
||||||
|
|
||||||
|
void setUp();
|
||||||
|
|
||||||
|
void tearDown();
|
||||||
|
|
||||||
|
void recordSamplePoint(const std::string& name, double value);
|
||||||
|
|
||||||
|
void writeRecord();
|
||||||
|
|
||||||
|
private:
|
||||||
|
DataLogger();
|
||||||
|
|
||||||
|
class DataLoggerPrivate;
|
||||||
|
std::unique_ptr<DataLoggerPrivate> d;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace FGTestApi
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "test_suite/dataStore.hxx"
|
#include "test_suite/dataStore.hxx"
|
||||||
|
|
||||||
|
#include "TestDataLogger.hxx"
|
||||||
#include "testGlobals.hxx"
|
#include "testGlobals.hxx"
|
||||||
|
|
||||||
#include <simgear/io/iostreams/sgstream.hxx>
|
#include <simgear/io/iostreams/sgstream.hxx>
|
||||||
|
@ -254,15 +255,30 @@ void setPositionAndStabilise(const SGGeod& g)
|
||||||
|
|
||||||
void runForTime(double t)
|
void runForTime(double t)
|
||||||
{
|
{
|
||||||
int ticks = t * 120.0;
|
const int tickHz = 30;
|
||||||
|
const double tickDuration = 1.0 / tickHz;
|
||||||
|
|
||||||
|
int ticks = static_cast<int>(t * tickHz);
|
||||||
assert(ticks > 0);
|
assert(ticks > 0);
|
||||||
const double dt = 1 / 120.0;
|
|
||||||
|
const int logInterval = 0.5 * tickHz;
|
||||||
|
int nextLog = 0;
|
||||||
|
|
||||||
for (int t = 0; t < ticks; ++t) {
|
for (int t = 0; t < ticks; ++t) {
|
||||||
globals->inc_sim_time_sec(dt);
|
globals->inc_sim_time_sec(tickDuration);
|
||||||
globals->get_subsystem_mgr()->update(dt);
|
globals->get_subsystem_mgr()->update(tickDuration);
|
||||||
if (global_loggingToKML) {
|
|
||||||
logCoordinate(globals->get_aircraft_position());
|
if (nextLog == 0) {
|
||||||
|
if (global_loggingToKML) {
|
||||||
|
logCoordinate(globals->get_aircraft_position());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DataLogger::isActive()) {
|
||||||
|
DataLogger::instance()->writeRecord();
|
||||||
|
}
|
||||||
|
nextLog = logInterval;
|
||||||
|
} else {
|
||||||
|
nextLog--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,22 +290,26 @@ bool runForTimeWithCheck(double t, RunCheck check)
|
||||||
|
|
||||||
int ticks = static_cast<int>(t * tickHz);
|
int ticks = static_cast<int>(t * tickHz);
|
||||||
assert(ticks > 0);
|
assert(ticks > 0);
|
||||||
const int logInterval = 2 * tickHz; // every two seconds
|
const int logInterval = 0.5 * tickHz;
|
||||||
int nextLog = 0;
|
int nextLog = 0;
|
||||||
|
|
||||||
for (int t = 0; t < ticks; ++t) {
|
for (int t = 0; t < ticks; ++t) {
|
||||||
globals->inc_sim_time_sec(tickDuration);
|
globals->inc_sim_time_sec(tickDuration);
|
||||||
globals->get_subsystem_mgr()->update(tickDuration);
|
globals->get_subsystem_mgr()->update(tickDuration);
|
||||||
|
|
||||||
if (global_loggingToKML) {
|
if (nextLog == 0) {
|
||||||
if (nextLog == 0) {
|
if (global_loggingToKML) {
|
||||||
logCoordinate(globals->get_aircraft_position());
|
logCoordinate(globals->get_aircraft_position());
|
||||||
nextLog = logInterval;
|
|
||||||
} else {
|
|
||||||
nextLog--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DataLogger::isActive()) {
|
||||||
|
DataLogger::instance()->writeRecord();
|
||||||
|
}
|
||||||
|
nextLog = logInterval;
|
||||||
|
} else {
|
||||||
|
nextLog--;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool done = check();
|
bool done = check();
|
||||||
if (done) {
|
if (done) {
|
||||||
if (global_loggingToKML) {
|
if (global_loggingToKML) {
|
||||||
|
|
Loading…
Reference in a new issue