1
0
Fork 0

Added support for analysing raw speed values when replaying.

Setting sim/replay/log-raw-speed logs raw speed to
/sim/replay/log-raw-speed-values/value[].

'scripts/python/recordreplay.py --test-motion' uses this to test for a bug
where the user aircraft simtime and signals information don't quite match in
continuous recordings.
This commit is contained in:
Julian Smith 2021-03-14 21:06:57 +00:00
parent 797f02f9dd
commit 5c76f41743
5 changed files with 181 additions and 57 deletions

View file

@ -26,6 +26,11 @@ Args:
A second fgfs executable. If specified we run all tests twice, first A second fgfs executable. If specified we run all tests twice, first
using <fgfs-old> to create the recording and <fgfs> to replay it, using <fgfs-old> to create the recording and <fgfs> to replay it,
second the other way round. second the other way round.
--test-motion
Checks that speed of aircraft on replay is not affected by frame rate.
We deliberately change frame rate while recording UFO moving at
constant speed.
BOOLS is comma-sparated list of 0 or 1, with 1 activating the particular BOOLS is comma-sparated list of 0 or 1, with 1 activating the particular
feature. So for example '--continuous 0' tests normal recording/replay', feature. So for example '--continuous 0' tests normal recording/replay',
@ -77,20 +82,23 @@ class Fg:
args += f' --telnet={port}' args += f' --telnet={port}'
args2 = args.split() args2 = args.split()
environ = os.environ.copy()
if isinstance(env, str): if isinstance(env, str):
env2 = dict()
for nv in env.split(): for nv in env.split():
n, v = nv.split('=', 1) n, v = nv.split('=', 1)
env2[n] = v environ[n] = v
env = env2
environ = os.environ.copy()
if env:
environ.update(env)
if 'DISPLAY' not in environ: if 'DISPLAY' not in environ:
environ['DISPLAY'] = ':0' environ['DISPLAY'] = ':0'
# Run flightgear in new process, telling it to open telnet server. # Run flightgear in new process, telling it to open telnet server.
self.child = subprocess.Popen(args2) #
# We run not in a shell, otherwise self.child.terminate() doesn't
# work - it would kill the shell but leave fgfs running (there are
# workarounds for this, such as prefixing the command with 'exec').
#
log(f'Command is: {args}')
log(f'Running: {args2}')
self.child = subprocess.Popen(args2, env=environ)
# Connect to flightgear's telnet server. # Connect to flightgear's telnet server.
timeout = 15 timeout = 15
@ -278,11 +286,80 @@ def test_record_replay(
log('Test passed') log('Test passed')
def test_motion(fgfs):
aircraft = 'ufo'
fg = Fg( aircraft, f'{fgfs} --aircraft={aircraft}')
path = f'{fg.aircraft}-continuous.fgtape'
if 0:
items = fg.fg.ls( '/sim')
log( '/sim is:')
for item in items:
log( f' {item}')
fg.close()
return
fg.waitfor('/sim/fdm-initialized', 1, timeout=45)
# Run UFO with constant speed, varying the framerate so we check whether
# recorded speeds are affected.
#
fg.fg['/controls/engines/engine[0]/throttle'] = 0.1
fg.fg['/sim/frame-rate-throttle-hz'] = 5
# Delay to let frame rate settle.
time.sleep(10)
# Start recording.
fg.fg['/sim/replay/record-continuous'] = 1
time.sleep(5)
# Change frame rate.
fg.fg['/sim/frame-rate-throttle-hz'] = 2
time.sleep(5)
# Restore original frame rate.
fg.fg['/sim/frame-rate-throttle-hz'] = 5
time.sleep(5)
# Stop recording.
fg.fg['/sim/replay/record-continuous'] = 0
fg.close()
path2 = os.readlink( path)
g_cleanup.append(lambda: os.remove(path2))
fg = Fg( aircraft, f'{fgfs} --load-tape={path} --prop:/sim/replay/log-raw-speed=true')
fg.waitfor('/sim/fdm-initialized', 1, timeout=45)
fg.fg['/sim/frame-rate-throttle-hz'] = 10
fg.waitfor('/sim/replay/replay-state', 1)
fg.waitfor('/sim/replay/replay-state-eof', 1)
items0 = fg.fg.ls( '/sim/replay/log-raw-speed-values')
items = []
for item in items0:
if item.name == 'value':
items.append(item)
num_errors = 0
for item in items[:-1]: # Ignore last item because replay at end interpolates.
speed = float(item.value)
prefix = ' '
if abs(speed - 200) > 0.5:
num_errors += 1
prefix = '*'
log( f' {prefix} speed={speed} details: {item}')
fg.close()
assert num_errors == 0, 'Replay showed uneven speed.'
if __name__ == '__main__': if __name__ == '__main__':
fgfs = f'./build-walk/fgfs.exe-run.sh' fgfs = f'./build-walk/fgfs.exe-run.sh'
fgfs_old = None fgfs_old = None
do_test = 'all'
continuous_s = [0, 1] continuous_s = [0, 1]
extra_properties_s = [0, 1] extra_properties_s = [0, 1]
main_view_s = [0, 1] main_view_s = [0, 1]
@ -319,9 +396,14 @@ if __name__ == '__main__':
elif arg == '--f-old': elif arg == '--f-old':
fgfs_old = next(args) fgfs_old = next(args)
fgfs_reverse = [0, 1] fgfs_reverse = [0, 1]
elif arg == '--test-motion':
do_test = 'motion'
else: else:
raise Exception(f'Unrecognised arg: {arg!r}') raise Exception(f'Unrecognised arg: {arg!r}')
if do_test == 'motion':
test_motion( fgfs)
elif do_test == 'all':
try: try:
if fgfs_old: if fgfs_old:
for fgfs1, fgfs2 in [(fgfs, fgfs_old), (fgfs_old, fgfs)]: for fgfs1, fgfs2 in [(fgfs, fgfs_old), (fgfs_old, fgfs)]:
@ -373,6 +455,8 @@ if __name__ == '__main__':
it += 1 it += 1
finally: finally:
pass pass
else:
assert 0, f'do_test={do_test}'
# If everything passed, cleanup. Otherwise leave recordings in place, as # If everything passed, cleanup. Otherwise leave recordings in place, as
# they can be useful for debugging. # they can be useful for debugging.

View file

@ -230,6 +230,7 @@ FGFlightRecorder::FGFlightRecorder(const char* pConfigName) :
m_ReplayMainWindowSize (fgGetNode("/sim/replay/replay-main-window-size", true)), m_ReplayMainWindowSize (fgGetNode("/sim/replay/replay-main-window-size", true)),
m_RecordContinuous (fgGetNode("/sim/replay/record-continuous", true)), m_RecordContinuous (fgGetNode("/sim/replay/record-continuous", true)),
m_RecordExtraProperties (fgGetNode("/sim/replay/record-extra-properties", true)), m_RecordExtraProperties (fgGetNode("/sim/replay/record-extra-properties", true)),
m_LogRawSpeed (fgGetNode("/sim/replay/log-raw-speed", true)),
m_TotalRecordSize(0), m_TotalRecordSize(0),
m_ConfigName(pConfigName), m_ConfigName(pConfigName),
m_usingDefaultConfig(false), m_usingDefaultConfig(false),
@ -775,6 +776,7 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
const double* pDoubles = (const double*) &pBuffer[Offset]; const double* pDoubles = (const double*) &pBuffer[Offset];
const double* pLastDoubles = (const double*) &pLastBuffer[Offset]; const double* pLastDoubles = (const double*) &pLastBuffer[Offset];
unsigned int SignalCount = m_CaptureDouble.size(); unsigned int SignalCount = m_CaptureDouble.size();
for (unsigned int i=0; i<SignalCount; i++) for (unsigned int i=0; i<SignalCount; i++)
{ {
double v = pDoubles[i]; double v = pDoubles[i];
@ -785,6 +787,38 @@ FGFlightRecorder::replay(double SimTime, const FGReplayData* _pNextBuffer,
} }
m_CaptureDouble[i].Signal->setDoubleValue(v); m_CaptureDouble[i].Signal->setDoubleValue(v);
} }
if (m_LogRawSpeed->getBoolValue()) {
// Log raw speed values to
// /sim/replay/log-raw-speed-values/value[]. This is used by
// scripts/python/recordreplay.py --test-motion.
//
SGGeod pos_geod = SGGeod::fromDegFt(
fgGetDouble("/position/longitude-deg"),
fgGetDouble("/position/latitude-deg"),
fgGetDouble("/position/altitude-ft")
);
SGVec3d pos = SGVec3d::fromGeod(pos_geod);
static SGVec3d pos_prev;
static double simtime_prev = -1;
double dt = SimTime - simtime_prev;
if (simtime_prev != -1 && dt > 0) {
double distance = length(pos - pos_prev);
double speed = dt ? distance / dt : -1;
SG_LOG(SG_GENERAL, SG_DEBUG, "User aircraft:"
<< " pLastBuffer=" << ((void*) pLastBuffer)
<< " SimTime=" << SimTime
<< " dt=" << dt
<< " distance=" << distance
<< " speed=" << speed
);
SGPropertyNode* n = fgGetNode("/sim/replay/log-raw-speed-values", true /*create*/);
n->addChild("value")->setDoubleValue(speed);
}
pos_prev = pos;
simtime_prev = SimTime;
}
Offset += SignalCount * sizeof(double); Offset += SignalCount * sizeof(double);
} }

View file

@ -95,6 +95,8 @@ private:
SGPropertyNode_ptr m_RecordContinuous; SGPropertyNode_ptr m_RecordContinuous;
SGPropertyNode_ptr m_RecordExtraProperties; SGPropertyNode_ptr m_RecordExtraProperties;
SGPropertyNode_ptr m_LogRawSpeed;
// This contains copy of all properties that we are recording, so that we // This contains copy of all properties that we are recording, so that we
// can send only differences. // can send only differences.
// //

View file

@ -356,6 +356,7 @@ FGReplay::init()
{ {
disable_replay = fgGetNode("/sim/replay/disable", true); disable_replay = fgGetNode("/sim/replay/disable", true);
replay_master = fgGetNode("/sim/replay/replay-state", true); replay_master = fgGetNode("/sim/replay/replay-state", true);
replay_master_eof = fgGetNode("/sim/replay/replay-state-eof", true);
replay_time = fgGetNode("/sim/replay/time", true); replay_time = fgGetNode("/sim/replay/time", true);
replay_time_str = fgGetNode("/sim/replay/time-str", true); replay_time_str = fgGetNode("/sim/replay/time-str", true);
replay_looped = fgGetNode("/sim/replay/looped", true); replay_looped = fgGetNode("/sim/replay/looped", true);
@ -552,6 +553,7 @@ FGReplay::start(bool NewTape)
if (0 == replay_master->getIntValue()) if (0 == replay_master->getIntValue())
{ {
replay_master->setIntValue(1); replay_master->setIntValue(1);
replay_master_eof->setIntValue(0);
if (NewTape) if (NewTape)
{ {
// start replay at initial time, when loading a new tape // start replay at initial time, when loading a new tape
@ -1010,6 +1012,7 @@ FGReplay::update( double dt )
{ {
if (!was_finished_already) if (!was_finished_already)
{ {
replay_master_eof->setIntValue(1);
guiMessage("End of tape. 'Esc' to return."); guiMessage("End of tape. 'Esc' to return.");
was_finished_already = true; was_finished_already = true;
} }

View file

@ -223,6 +223,7 @@ private:
SGPropertyNode_ptr disable_replay; SGPropertyNode_ptr disable_replay;
SGPropertyNode_ptr replay_master; SGPropertyNode_ptr replay_master;
SGPropertyNode_ptr replay_master_eof;
SGPropertyNode_ptr replay_time; SGPropertyNode_ptr replay_time;
SGPropertyNode_ptr replay_time_str; SGPropertyNode_ptr replay_time_str;
SGPropertyNode_ptr replay_looped; SGPropertyNode_ptr replay_looped;