From c7b28a196036edf66f98b3a8ef6596436c288975 Mon Sep 17 00:00:00 2001 From: James Turner <zakalawe@mac.com> Date: Wed, 10 Oct 2018 09:26:06 +0100 Subject: [PATCH] UNit-tests for the NavRadio Still needs ILS and GS tests, but starting to be useful already. --- src/Sound/audioident.cxx | 19 +- test_suite/FGTestApi/globals.cxx | 8 +- test_suite/FGTestApi/globals.hxx | 3 + test_suite/unit_tests/CMakeLists.txt | 1 + test_suite/unit_tests/Input/test_hidinput.cxx | 2 +- .../unit_tests/Instrumentation/CMakeLists.txt | 12 + .../unit_tests/Instrumentation/TestSuite.cxx | 23 ++ .../Instrumentation/test_navRadio.cxx | 206 ++++++++++++++++++ .../Instrumentation/test_navRadio.hxx | 67 ++++++ 9 files changed, 335 insertions(+), 6 deletions(-) create mode 100644 test_suite/unit_tests/Instrumentation/CMakeLists.txt create mode 100644 test_suite/unit_tests/Instrumentation/TestSuite.cxx create mode 100644 test_suite/unit_tests/Instrumentation/test_navRadio.cxx create mode 100644 test_suite/unit_tests/Instrumentation/test_navRadio.hxx diff --git a/src/Sound/audioident.cxx b/src/Sound/audioident.cxx index aa8a97ac7..bb924c26b 100644 --- a/src/Sound/audioident.cxx +++ b/src/Sound/audioident.cxx @@ -37,22 +37,29 @@ AudioIdent::AudioIdent( const std::string & fx_name, const double interval_secs, void AudioIdent::init() { + auto soundManager = globals->get_subsystem<SGSoundMgr>(); + if (!soundManager) + return; // sound disabled + _timer = 0.0; _ident = ""; _running = false; - _sgr = globals->get_subsystem<SGSoundMgr>()->find("avionics", true); + _sgr = soundManager->find("avionics", true); _sgr->tie_to_listener(); } void AudioIdent::stop() { - if( _sgr->exists( _fx_name ) ) + if (_sgr && _sgr->exists( _fx_name ) ) _sgr->stop( _fx_name ); _running = false; } void AudioIdent::start() { + if (!_sgr) + return; + _timer = _interval; _sgr->play_once(_fx_name); _running = true; @@ -60,10 +67,11 @@ void AudioIdent::start() void AudioIdent::setVolumeNorm( double volumeNorm ) { + if (!_sgr) + return; + SG_CLAMP_RANGE(volumeNorm, 0.0, 1.0); - SGSoundSample *sound = _sgr->find( _fx_name ); - if ( sound != NULL ) { sound->set_volume( volumeNorm ); } @@ -71,6 +79,9 @@ void AudioIdent::setVolumeNorm( double volumeNorm ) void AudioIdent::setIdent( const std::string & ident, double volumeNorm ) { + if (!_sgr) + return; + // Signal may flicker very frequently (due to our realistic newnavradio...). // Avoid recreating identical sound samples all the time, instead turn off // volume when signal is lost, and save the most recent sample. diff --git a/test_suite/FGTestApi/globals.cxx b/test_suite/FGTestApi/globals.cxx index cb0257727..4b8bf0972 100644 --- a/test_suite/FGTestApi/globals.cxx +++ b/test_suite/FGTestApi/globals.cxx @@ -15,7 +15,7 @@ #include <simgear/structure/event_mgr.hxx> #include <simgear/timing/timestamp.hxx> - +#include <simgear/math/sg_geodesy.hxx> namespace FGTestApi { @@ -59,6 +59,12 @@ void initTestGlobals(const std::string& testName) } // End of namespace setUp. +void setPosition(const SGGeod& g) +{ + globals->get_props()->setDoubleValue("position/latitude-deg", g.getLatitudeDeg()); + globals->get_props()->setDoubleValue("position/longitude-deg", g.getLongitudeDeg()); + globals->get_props()->setDoubleValue("position/altitude-ft", g.getElevationFt()); +} namespace tearDown { diff --git a/test_suite/FGTestApi/globals.hxx b/test_suite/FGTestApi/globals.hxx index 14ae13dbf..947e5eb08 100644 --- a/test_suite/FGTestApi/globals.hxx +++ b/test_suite/FGTestApi/globals.hxx @@ -3,6 +3,8 @@ #include <string> +class SGGeod; + namespace FGTestApi { namespace setUp { @@ -11,6 +13,7 @@ void initTestGlobals(const std::string& testName); } // End of namespace setUp. +void setPosition(const SGGeod& g); namespace tearDown { diff --git a/test_suite/unit_tests/CMakeLists.txt b/test_suite/unit_tests/CMakeLists.txt index 8d2958847..da57f3367 100644 --- a/test_suite/unit_tests/CMakeLists.txt +++ b/test_suite/unit_tests/CMakeLists.txt @@ -6,6 +6,7 @@ foreach( unit_test_category Input Main Navaids + Instrumentation Scripting ) diff --git a/test_suite/unit_tests/Input/test_hidinput.cxx b/test_suite/unit_tests/Input/test_hidinput.cxx index 2a586844e..0a58d1143 100644 --- a/test_suite/unit_tests/Input/test_hidinput.cxx +++ b/test_suite/unit_tests/Input/test_hidinput.cxx @@ -20,7 +20,7 @@ #include "test_hidinput.hxx" -#include "test_suite/helpers/globals.hxx" +#include "test_suite/FGTestApi/globals.hxx" #include <simgear/misc/test_macros.hxx> diff --git a/test_suite/unit_tests/Instrumentation/CMakeLists.txt b/test_suite/unit_tests/Instrumentation/CMakeLists.txt new file mode 100644 index 000000000..610ed2867 --- /dev/null +++ b/test_suite/unit_tests/Instrumentation/CMakeLists.txt @@ -0,0 +1,12 @@ +set(TESTSUITE_SOURCES + ${TESTSUITE_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/test_navRadio.cxx + PARENT_SCOPE +) + +set(TESTSUITE_HEADERS + ${TESTSUITE_HEADERS} + ${CMAKE_CURRENT_SOURCE_DIR}/test_navRadio.hxx + PARENT_SCOPE +) diff --git a/test_suite/unit_tests/Instrumentation/TestSuite.cxx b/test_suite/unit_tests/Instrumentation/TestSuite.cxx new file mode 100644 index 000000000..e51d4c35c --- /dev/null +++ b/test_suite/unit_tests/Instrumentation/TestSuite.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2018 Edward d'Auvergne + * + * 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 "test_navRadio.hxx" + +// Set up the unit tests. +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(NavRadioTests, "Unit tests"); diff --git a/test_suite/unit_tests/Instrumentation/test_navRadio.cxx b/test_suite/unit_tests/Instrumentation/test_navRadio.cxx new file mode 100644 index 000000000..7ca2f6b3c --- /dev/null +++ b/test_suite/unit_tests/Instrumentation/test_navRadio.cxx @@ -0,0 +1,206 @@ +#include "test_navRadio.hxx" + +#include <memory> +#include <cstring> + +#include "test_suite/FGTestApi/globals.hxx" +#include "test_suite/FGTestApi/NavDataCache.hxx" + +#include <Navaids/NavDataCache.hxx> +#include <Navaids/navrecord.hxx> +#include <Navaids/navlist.hxx> + +#include <Instrumentation/navradio.hxx> + +// Set up function for each test. +void NavRadioTests::setUp() +{ + FGTestApi::setUp::initTestGlobals("navradio"); + FGTestApi::setUp::initNavDataCache(); +} + + +// Clean up after each test. +void NavRadioTests::tearDown() +{ + FGTestApi::tearDown::shutdownTestGlobals(); +} + +void NavRadioTests::setPositionAndStabilise(FGNavRadio* r, const SGGeod& g) +{ + FGTestApi::setPosition(g); + for (int i=0; i<60; ++i) { + r->update(0.1); + } +} + +void NavRadioTests::testBasic() +{ + SGPropertyNode_ptr configNode(new SGPropertyNode); + configNode->setStringValue("name", "navtest"); + configNode->setIntValue("number", 2); + std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode)); + + r->bind(); + r->init(); + + SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]"); + node->setBoolValue("serviceable", true); + // needed for the radio to power up + globals->get_props()->setDoubleValue("systems/electrical/outputs/navtest", 6.0); + node->setDoubleValue("frequencies/selected-mhz", 113.8); + + SGGeod pos = SGGeod::fromDegFt(-3.352780, 55.499199, 20000); + setPositionAndStabilise(r.get(), pos); + + CPPUNIT_ASSERT(!strcmp("TLA", node->getStringValue("nav-id"))); + CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range")); +} + +void NavRadioTests::testCDIDeflection() +{ + SGPropertyNode_ptr configNode(new SGPropertyNode); + configNode->setStringValue("name", "navtest"); + configNode->setIntValue("number", 2); + std::unique_ptr<FGNavRadio> r(new FGNavRadio(configNode)); + + r->bind(); + r->init(); + + SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/navtest[2]"); + node->setBoolValue("serviceable", true); + // needed for the radio to power up + globals->get_props()->setDoubleValue("systems/electrical/outputs/navtest", 6.0); + node->setDoubleValue("frequencies/selected-mhz", 113.55); + + node->setDoubleValue("radials/selected-deg", 25); + + FGPositioned::TypeFilter f{FGPositioned::VOR}; + FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent("MCT", SGGeod::fromDeg(-2.26, 53.3), &f)); + + // twist of MCT is 5.0, so we use a bearing of 20 here, not 25 + SGGeod posOnRadial = SGGeodesy::direct(nav->geod(), 20.0, 10 * SG_NM_TO_METER); + posOnRadial.setElevationFt(10000); + setPositionAndStabilise(r.get(), posOnRadial); + + CPPUNIT_ASSERT(!strcmp("MCT", node->getStringValue("nav-id"))); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("heading-needle-deflection"), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("heading-needle-deflection-norm"), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("crosstrack-error-m"), 0.01); + CPPUNIT_ASSERT(node->getBoolValue("from-flag")); + CPPUNIT_ASSERT(!node->getBoolValue("to-flag")); + + + // move off course + SGGeod posOffRadial = SGGeodesy::direct(nav->geod(), 15.0, 20 * SG_NM_TO_METER); // 5 degress off + posOffRadial.setElevationFt(12000); + setPositionAndStabilise(r.get(), posOffRadial); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, node->getDoubleValue("heading-needle-deflection"), 0.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, node->getDoubleValue("heading-needle-deflection-norm"), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01); + + double xtkE = sin(5.0 * SG_DEGREES_TO_RADIANS) * 20 * SG_NM_TO_METER; + CPPUNIT_ASSERT_DOUBLES_EQUAL(xtkE, node->getDoubleValue("crosstrack-error-m"), 50.0); + CPPUNIT_ASSERT(node->getBoolValue("from-flag")); + CPPUNIT_ASSERT(!node->getBoolValue("to-flag")); + + posOffRadial = SGGeodesy::direct(nav->geod(), 28.0, 30 * SG_NM_TO_METER); // 8 degress off + posOffRadial.setElevationFt(16000); + setPositionAndStabilise(r.get(), posOffRadial); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-8.0, node->getDoubleValue("heading-needle-deflection"), 0.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.8, node->getDoubleValue("heading-needle-deflection-norm"), 0.01); + + xtkE = sin(-8.0 * SG_DEGREES_TO_RADIANS) * 30 * SG_NM_TO_METER; + CPPUNIT_ASSERT_DOUBLES_EQUAL(xtkE, node->getDoubleValue("crosstrack-error-m"), 50.0); + + // move more than ten degrees off course + posOffRadial = SGGeodesy::direct(nav->geod(), 33.0, 40 * SG_NM_TO_METER); // 13 degress off + posOffRadial.setElevationFt(16000); + setPositionAndStabilise(r.get(), posOffRadial); + + // pegged to full deflection + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-10.0, node->getDoubleValue("heading-needle-deflection"), 0.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, node->getDoubleValue("heading-needle-deflection-norm"), 0.01); + + // cross track error is computed based on true deflection, not clamped + xtkE = sin(-13.0 * SG_DEGREES_TO_RADIANS) * 40 * SG_NM_TO_METER; + CPPUNIT_ASSERT_DOUBLES_EQUAL(xtkE, node->getDoubleValue("crosstrack-error-m"), 50.0); + CPPUNIT_ASSERT(node->getBoolValue("from-flag")); + CPPUNIT_ASSERT(!node->getBoolValue("to-flag")); + +// try on the TO side of the station + // let's use Perth VOR, but the Australian one to check southern + // hemisphere operation + node->setDoubleValue("frequencies/selected-mhz", 113.7); + node->setDoubleValue("radials/selected-deg", 42.0); // twist is -2.0 + CPPUNIT_ASSERT(!strcmp("113.70", node->getStringValue("frequencies/selected-mhz-fmt"))); + + auto perthVOR = fgpositioned_cast<FGNavRecord>( + FGPositioned::findClosestWithIdent("PH", SGGeod::fromDeg(115.95, -31.9), &f)); + + SGGeod p = SGGeodesy::direct(perthVOR->geod(), 220.0, 20 * SG_NM_TO_METER); // on the reciprocal radial + p.setElevationFt(12000); + setPositionAndStabilise(r.get(), p); + + CPPUNIT_ASSERT(!strcmp("PH", node->getStringValue("nav-id"))); + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(40.0, node->getDoubleValue("heading-deg"), 0.5); + + // actual radial has twist subtracted + CPPUNIT_ASSERT_DOUBLES_EQUAL(222.0, node->getDoubleValue("radials/actual-deg"), 0.01); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("heading-needle-deflection"), 0.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("heading-needle-deflection-norm"), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("crosstrack-error-m"), 50.0); + CPPUNIT_ASSERT(!node->getBoolValue("from-flag")); + CPPUNIT_ASSERT(node->getBoolValue("to-flag")); + +// off course on the TO side + p = SGGeodesy::direct(perthVOR->geod(), 227.0, 100 * SG_NM_TO_METER); + p.setElevationFt(18000); + setPositionAndStabilise(r.get(), p); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01); + CPPUNIT_ASSERT_DOUBLES_EQUAL(47.0, node->getDoubleValue("heading-deg"), 1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(229.0, node->getDoubleValue("radials/actual-deg"), 0.01); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(7.0, node->getDoubleValue("heading-needle-deflection"), 0.1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.7, node->getDoubleValue("heading-needle-deflection-norm"), 0.01); + + xtkE = sin(7.0 * SG_DEGREES_TO_RADIANS) * 100 * SG_NM_TO_METER; + CPPUNIT_ASSERT_DOUBLES_EQUAL(xtkE, node->getDoubleValue("crosstrack-error-m"), 50.0); + CPPUNIT_ASSERT(!node->getBoolValue("from-flag")); + CPPUNIT_ASSERT(node->getBoolValue("to-flag")); +} + +void NavRadioTests::testILSBasic() +{ + + // also check ILS back course +} + +void NavRadioTests::testGS() +{ + +} + +void NavRadioTests::testILSFalseCourse() +{ + + // also GS false lobes +} + +void NavRadioTests::testILSPaired() +{ + // EGPH and countless more +} + +void NavRadioTests::testILSAdjacentPaired() +{ + // eg KJFK +} diff --git a/test_suite/unit_tests/Instrumentation/test_navRadio.hxx b/test_suite/unit_tests/Instrumentation/test_navRadio.hxx new file mode 100644 index 000000000..200653647 --- /dev/null +++ b/test_suite/unit_tests/Instrumentation/test_navRadio.hxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 Edward d'Auvergne + * + * 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/>. + */ + + +#ifndef _FG_NAVRADIO_UNIT_TESTS_HXX +#define _FG_NAVRADIO_UNIT_TESTS_HXX + + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestFixture.h> + +class FGNavRadio; +class SGGeod; + +// The flight plan unit tests. +class NavRadioTests : public CppUnit::TestFixture +{ + // Set up the test suite. + CPPUNIT_TEST_SUITE(NavRadioTests); + CPPUNIT_TEST(testBasic); + CPPUNIT_TEST(testCDIDeflection); + + CPPUNIT_TEST(testILSBasic); + CPPUNIT_TEST(testGS); + CPPUNIT_TEST(testILSFalseCourse); + CPPUNIT_TEST(testILSPaired); + CPPUNIT_TEST(testILSAdjacentPaired); + + CPPUNIT_TEST_SUITE_END(); + + void setPositionAndStabilise(FGNavRadio* r, const SGGeod& g); + +public: + // Set up function for each test. + void setUp(); + + // Clean up after each test. + void tearDown(); + + // The tests. + void testBasic(); + void testCDIDeflection(); + + void testILSBasic(); + void testGS(); + void testILSFalseCourse(); + void testILSPaired(); + void testILSAdjacentPaired(); +}; + +#endif // _FG_NAVRADIO_UNIT_TESTS_HXX