first commit
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
set(TESTSUITE_SOURCES
|
||||
${TESTSUITE_SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_navRadio.cxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_gps.cxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_hold_controller.cxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_rnav_procedures.cxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_dme.cxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_commRadio.cxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_transponder.cxx
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
set(TESTSUITE_HEADERS
|
||||
${TESTSUITE_HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_navRadio.hxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_gps.hxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_hold_controller.hxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_rnav_procedures.hxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_dme.hxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_commRadio.hxx
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_transponder.hxx
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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_commRadio.hxx"
|
||||
#include "test_dme.hxx"
|
||||
#include "test_gps.hxx"
|
||||
#include "test_hold_controller.hxx"
|
||||
#include "test_navRadio.hxx"
|
||||
#include "test_rnav_procedures.hxx"
|
||||
#include "test_transponder.hxx"
|
||||
|
||||
// Set up the unit tests.
|
||||
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(NavRadioTests, "Unit tests");
|
||||
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(GPSTests, "Unit tests");
|
||||
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(HoldControllerTests, "Unit tests");
|
||||
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(RNAVProcedureTests, "Unit tests");
|
||||
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(DMEReceiverTests, "Unit tests");
|
||||
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CommRadioTests, "Unit tests");
|
||||
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(TransponderTests, "Unit tests");
|
||||
@@ -0,0 +1,207 @@
|
||||
#include "test_commRadio.hxx"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "test_suite/FGTestApi/NavDataCache.hxx"
|
||||
#include "test_suite/FGTestApi/testGlobals.hxx"
|
||||
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Navaids/NavDataCache.hxx>
|
||||
|
||||
#include <Instrumentation/commradio.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/locale.hxx>
|
||||
|
||||
// Set up function for each test.
|
||||
void CommRadioTests::setUp()
|
||||
{
|
||||
FGTestApi::setUp::initTestGlobals("commradio");
|
||||
FGTestApi::setUp::initNavDataCache();
|
||||
|
||||
// otherwise ATCSPeech will call locale functions and assert
|
||||
globals->get_locale()->selectLanguage({});
|
||||
}
|
||||
|
||||
|
||||
// Clean up after each test.
|
||||
void CommRadioTests::tearDown()
|
||||
{
|
||||
FGTestApi::tearDown::shutdownTestGlobals();
|
||||
}
|
||||
|
||||
|
||||
// std::string NavRadioTests::formatFrequency(double f)
|
||||
// {
|
||||
// char buf[16];
|
||||
// ::snprintf(buf, 16, "%3.2f", f);
|
||||
// return buf;
|
||||
// }
|
||||
|
||||
SGSubsystemRef CommRadioTests::setupStandardRadio(const std::string& name, int index, bool enable833)
|
||||
{
|
||||
SGPropertyNode_ptr configNode(new SGPropertyNode);
|
||||
configNode->setStringValue("name", name);
|
||||
configNode->setIntValue("number", index);
|
||||
configNode->setBoolValue("eight-point-three", enable833);
|
||||
auto r = Instrumentation::CommRadio::createInstance(configNode);
|
||||
|
||||
fgSetBool("/sim/atis/enabled", false);
|
||||
|
||||
r->bind();
|
||||
r->init();
|
||||
|
||||
globals->add_subsystem("comm-radio", r, SGSubsystemMgr::GENERAL);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void CommRadioTests::testBasic()
|
||||
{
|
||||
auto r = setupStandardRadio("commtest", 2, false);
|
||||
|
||||
FGAirportRef apt = FGAirport::getByIdent("EDDM");
|
||||
FGTestApi::setPositionAndStabilise(apt->geod());
|
||||
|
||||
SGPropertyNode_ptr n = globals->get_props()->getNode("instrumentation/commtest[2]");
|
||||
// EDDM ATIS
|
||||
n->setDoubleValue("frequencies/selected-mhz", 123.125);
|
||||
r->update(1.0);
|
||||
|
||||
// CPPUNIT_ASSERT_DOUBLES_EQUAL(25, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
|
||||
CPPUNIT_ASSERT_EQUAL("123.12"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL("EDDM"s, string{n->getStringValue("airport-id")});
|
||||
CPPUNIT_ASSERT_EQUAL("ATIS"s, string{n->getStringValue("station-name")});
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, n->getDoubleValue("slant-distance-m"), 1e-6);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, n->getDoubleValue("signal-quality-norm"), 1e-6);
|
||||
|
||||
|
||||
n->setDoubleValue("frequencies/selected-mhz", 121.72);
|
||||
r->update(1.0);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL("121.72"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL("EDDM"s, string{n->getStringValue("airport-id")});
|
||||
CPPUNIT_ASSERT_EQUAL("CLNC DEL"s, string{n->getStringValue("station-name")});
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, n->getDoubleValue("slant-distance-m"), 1e-6);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, n->getDoubleValue("signal-quality-norm"), 1e-6);
|
||||
}
|
||||
|
||||
void CommRadioTests::testEightPointThree()
|
||||
{
|
||||
auto r = setupStandardRadio("commtest", 2, true);
|
||||
|
||||
|
||||
FGAirportRef apt = FGAirport::getByIdent("EGKK");
|
||||
FGTestApi::setPositionAndStabilise(apt->geod());
|
||||
|
||||
SGPropertyNode_ptr n = globals->get_props()->getNode("instrumentation/commtest[2]");
|
||||
|
||||
// EGKK ATIS
|
||||
n->setDoubleValue("frequencies/selected-mhz", 136.525);
|
||||
|
||||
r->update(1.0);
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(25, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
|
||||
CPPUNIT_ASSERT_EQUAL("136.525"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
|
||||
// random 8.3Khz station
|
||||
|
||||
n->setDoubleValue("frequencies/selected-mhz", 120.11);
|
||||
r->update(1.0);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
|
||||
CPPUNIT_ASSERT_EQUAL("120.110"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
CPPUNIT_ASSERT_EQUAL(338, n->getIntValue("frequencies/selected-channel"));
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(120.10833, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
|
||||
|
||||
// select station by channel, on 8.3khz boundary
|
||||
n->setIntValue("frequencies/selected-channel", 2561);
|
||||
r->update(1.0);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
|
||||
CPPUNIT_ASSERT_EQUAL("134.005"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
CPPUNIT_ASSERT_EQUAL(2561, n->getIntValue("frequencies/selected-channel"));
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(134.000, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
|
||||
|
||||
// select station by channel, on 25Khz boundary
|
||||
n->setIntValue("frequencies/selected-channel", 2560);
|
||||
r->update(1.0);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(25, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
|
||||
CPPUNIT_ASSERT_EQUAL("134.000"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
CPPUNIT_ASSERT_EQUAL(2560, n->getIntValue("frequencies/selected-channel"));
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(134.000, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
|
||||
|
||||
// select by frequency
|
||||
n->setDoubleValue("frequencies/selected-mhz", 120.035);
|
||||
r->update(1.0);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
|
||||
CPPUNIT_ASSERT_EQUAL("120.035"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
CPPUNIT_ASSERT_EQUAL(326, n->getIntValue("frequencies/selected-channel"));
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(120.03333, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
|
||||
|
||||
// under-run the permitted frequency range
|
||||
n->setDoubleValue("frequencies/selected-mhz", 117.99);
|
||||
r->update(1.0);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(25.0, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
|
||||
CPPUNIT_ASSERT_EQUAL(0, n->getIntValue("frequencies/selected-channel"));
|
||||
|
||||
n->setDoubleValue("frequencies/selected-mhz", 118.705);
|
||||
r->update(1.0);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
|
||||
CPPUNIT_ASSERT_EQUAL("118.705"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
CPPUNIT_ASSERT_EQUAL(113, n->getIntValue("frequencies/selected-channel"));
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(118.700, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
|
||||
|
||||
// over-run the frequency range
|
||||
n->setDoubleValue("frequencies/selected-mhz", 137.000);
|
||||
r->update(1.0);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(8.33, n->getDoubleValue("frequencies/selected-channel-width-khz"), 1e-3);
|
||||
CPPUNIT_ASSERT_EQUAL("136.990"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
CPPUNIT_ASSERT_EQUAL(3039, n->getIntValue("frequencies/selected-channel"));
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(136.99166, n->getDoubleValue("frequencies/selected-real-frequency-mhz"), 1e-6);
|
||||
}
|
||||
|
||||
void CommRadioTests::testEPLLTuning833()
|
||||
{
|
||||
// this test is disabled until data entry for EPLL is fixed
|
||||
return;
|
||||
|
||||
auto r = setupStandardRadio("commtest", 2, true);
|
||||
|
||||
FGAirportRef apt = FGAirport::getByIdent("EPLL");
|
||||
FGTestApi::setPositionAndStabilise(apt->geod());
|
||||
|
||||
SGPropertyNode_ptr n = globals->get_props()->getNode("instrumentation/commtest[2]");
|
||||
// should be EPLL TWR
|
||||
n->setDoubleValue("frequencies/selected-mhz", 124.225);
|
||||
r->update(1.0);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL("EPLL"s, string{n->getStringValue("airport-id")});
|
||||
CPPUNIT_ASSERT_EQUAL("Lodz TOWER"s, string{n->getStringValue("station-name")});
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, n->getDoubleValue("slant-distance-m"), 1e-6);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, n->getDoubleValue("signal-quality-norm"), 1e-6);
|
||||
}
|
||||
|
||||
void CommRadioTests::testEPLLTuning25()
|
||||
{
|
||||
auto r = setupStandardRadio("commtest", 2, false);
|
||||
|
||||
FGAirportRef apt = FGAirport::getByIdent("EPLL");
|
||||
FGTestApi::setPositionAndStabilise(apt->geod());
|
||||
|
||||
SGPropertyNode_ptr n = globals->get_props()->getNode("instrumentation/commtest[2]");
|
||||
// should be EPLL TWR
|
||||
n->setDoubleValue("frequencies/selected-mhz", 124.23);
|
||||
r->update(1.0);
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(124.23, n->getDoubleValue("frequencies/selected-mhz"), 1e-6);
|
||||
CPPUNIT_ASSERT_EQUAL("124.22"s, string{n->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
|
||||
// fail for now
|
||||
#if 0
|
||||
CPPUNIT_ASSERT_EQUAL("EPLL"s, string{n->getStringValue("airport-id")});
|
||||
CPPUNIT_ASSERT_EQUAL("Lodz TOWER"s, string{n->getStringValue("station-name")});
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, n->getDoubleValue("slant-distance-m"), 1e-6);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, n->getDoubleValue("signal-quality-norm"), 1e-6);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
|
||||
class FGNavRadio;
|
||||
class SGGeod;
|
||||
|
||||
// The flight plan unit tests.
|
||||
class CommRadioTests : public CppUnit::TestFixture
|
||||
{
|
||||
// Set up the test suite.
|
||||
CPPUNIT_TEST_SUITE(CommRadioTests);
|
||||
|
||||
CPPUNIT_TEST(testBasic);
|
||||
CPPUNIT_TEST(testEightPointThree);
|
||||
CPPUNIT_TEST(testEPLLTuning833);
|
||||
CPPUNIT_TEST(testEPLLTuning25);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
SGSubsystemRef setupStandardRadio(const std::string& name, int index, bool enable833);
|
||||
|
||||
public:
|
||||
// Set up function for each test.
|
||||
void setUp();
|
||||
|
||||
// Clean up after each test.
|
||||
void tearDown();
|
||||
|
||||
// std::string formatFrequency(double f);
|
||||
|
||||
// The tests.
|
||||
void testBasic();
|
||||
void testEightPointThree();
|
||||
void testEPLLTuning833();
|
||||
void testEPLLTuning25();
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
#include "test_dme.hxx"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "test_suite/FGTestApi/NavDataCache.hxx"
|
||||
#include "test_suite/FGTestApi/TestPilot.hxx"
|
||||
#include "test_suite/FGTestApi/testGlobals.hxx"
|
||||
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Navaids/NavDataCache.hxx>
|
||||
#include <Navaids/navlist.hxx>
|
||||
#include <Navaids/navrecord.hxx>
|
||||
|
||||
#include <Instrumentation/dme.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
// Set up function for each test.
|
||||
void DMEReceiverTests::setUp()
|
||||
{
|
||||
FGTestApi::setUp::initTestGlobals("navradio");
|
||||
FGTestApi::setUp::initNavDataCache();
|
||||
}
|
||||
|
||||
|
||||
// Clean up after each test.
|
||||
void DMEReceiverTests::tearDown()
|
||||
{
|
||||
FGTestApi::tearDown::shutdownTestGlobals();
|
||||
}
|
||||
|
||||
void DMEReceiverTests::setPositionAndStabilise(DME* r, const SGGeod& g)
|
||||
{
|
||||
FGTestApi::setPosition(g);
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
r->update(0.1);
|
||||
}
|
||||
}
|
||||
|
||||
SGSharedPtr<DME> DMEReceiverTests::setupStandardDME()
|
||||
{
|
||||
SGPropertyNode_ptr configNode(new SGPropertyNode);
|
||||
configNode->setStringValue("name", "dmetest");
|
||||
configNode->setIntValue("number", 2);
|
||||
|
||||
return new DME(configNode);
|
||||
}
|
||||
|
||||
void DMEReceiverTests::testBasic()
|
||||
{
|
||||
SGSharedPtr<DME> r = setupStandardDME();
|
||||
|
||||
// set a source string pointing at a fictious nav-receiver
|
||||
fgSetString("/instrumentation/dmetest[2]/frequencies/source",
|
||||
"/instrumentation/nav[0]/frequencies/selected-mhz");
|
||||
|
||||
r->bind();
|
||||
r->init();
|
||||
globals->get_subsystem_mgr()->add("dme", r.get());
|
||||
|
||||
auto arlanda = fgFindAirportID("ESSA");
|
||||
|
||||
// set the nav frequency
|
||||
fgSetDouble("/instrumentation/nav[0]/frequencies/selected-mhz", 113.30);
|
||||
|
||||
|
||||
SGPropertyNode_ptr node = globals->get_props()->getNode("instrumentation/dmetest[2]");
|
||||
node->setBoolValue("serviceable", true);
|
||||
|
||||
fgSetDouble("systems/electrical/outputs/dme", 12.0);
|
||||
|
||||
setPositionAndStabilise(r.get(), arlanda->geod());
|
||||
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(4.4, node->getDoubleValue("indicated-distance-nm"), 0.1);
|
||||
|
||||
// fly towards the station at constant speed
|
||||
|
||||
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
|
||||
pilot->setSpeedKts(150);
|
||||
|
||||
|
||||
FGPositioned::TypeFilter f{FGPositioned::DME};
|
||||
FGNavRecordRef arlandaDME = fgpositioned_cast<FGNavRecord>(
|
||||
FGPositioned::findClosestWithIdent("ANE", arlanda->geod(), &f));
|
||||
|
||||
const double trueCourseToANE = SGGeodesy::courseDeg(arlanda->geod(), arlandaDME->geod());
|
||||
pilot->setCourseTrue(trueCourseToANE);
|
||||
FGTestApi::runForTime(30.0);
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(150, node->getDoubleValue("indicated-ground-speed-kt"), 0.5);
|
||||
// should have travelled (150 / 3600 * 30 ) = 1.25nm
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(3.15, node->getDoubleValue("indicated-distance-nm"), 0.1);
|
||||
}
|
||||
|
||||
void DMEReceiverTests::testRCFN_04DME()
|
||||
{
|
||||
// disabled pending discussion about the data for this one
|
||||
return;
|
||||
|
||||
auto rcfn = fgFindAirportID("RCFN");
|
||||
|
||||
auto dmeReceiver = setupStandardDME();
|
||||
|
||||
FGRunwayRef rwy04 = rcfn->getRunwayByIdent("04");
|
||||
|
||||
FGPositioned::TypeFilter filter(FGPositioned::DME);
|
||||
auto matches = FGPositioned::findAllWithIdent("IFNN", &filter);
|
||||
FGPositioned::sortByRange(matches, rcfn->geod());
|
||||
|
||||
CPPUNIT_ASSERT(!matches.empty());
|
||||
// should be size two, really
|
||||
|
||||
auto station = fgpositioned_cast<FGNavRecord>(matches.front());
|
||||
CPPUNIT_ASSERT(station);
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(110.9, station->get_freq(), 0.01);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2021 James Turner <james@flightgear.org>
|
||||
*
|
||||
* 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 <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
|
||||
class DME;
|
||||
class SGGeod;
|
||||
|
||||
// The DME unit tests.
|
||||
class DMEReceiverTests : public CppUnit::TestFixture
|
||||
{
|
||||
// Set up the test suite.
|
||||
CPPUNIT_TEST_SUITE(DMEReceiverTests);
|
||||
CPPUNIT_TEST(testBasic);
|
||||
CPPUNIT_TEST(testRCFN_04DME);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
void setPositionAndStabilise(DME* r, const SGGeod& g);
|
||||
|
||||
SGSharedPtr<DME> setupStandardDME();
|
||||
|
||||
public:
|
||||
// Set up function for each test.
|
||||
void setUp();
|
||||
|
||||
// Clean up after each test.
|
||||
void tearDown();
|
||||
|
||||
// The tests.
|
||||
void testBasic();
|
||||
void testRCFN_04DME();
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _FG_GPS_UNIT_TESTS_HXX
|
||||
#define _FG_GPS_UNIT_TESTS_HXX
|
||||
|
||||
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <cppunit/TestFixture.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
class SGGeod;
|
||||
class GPS;
|
||||
|
||||
// The flight plan unit tests.
|
||||
class GPSTests : public CppUnit::TestFixture
|
||||
{
|
||||
// Set up the test suite.
|
||||
CPPUNIT_TEST_SUITE(GPSTests);
|
||||
CPPUNIT_TEST(testBasic);
|
||||
CPPUNIT_TEST(testNavRadioSlave);
|
||||
CPPUNIT_TEST(testTurnAnticipation);
|
||||
CPPUNIT_TEST(testOBSMode);
|
||||
CPPUNIT_TEST(testDirectTo);
|
||||
CPPUNIT_TEST(testLegMode);
|
||||
CPPUNIT_TEST(testDirectToLegOnFlightplan);
|
||||
CPPUNIT_TEST(testLongLeg);
|
||||
CPPUNIT_TEST(testLongLegWestbound);
|
||||
CPPUNIT_TEST(testOffsetFlight);
|
||||
CPPUNIT_TEST(testOverflightSequencing);
|
||||
CPPUNIT_TEST(testOffcourseSequencing);
|
||||
CPPUNIT_TEST(testLegIntercept);
|
||||
CPPUNIT_TEST(testDirectToLegOnFlightplanAndResumeBuiltin);
|
||||
CPPUNIT_TEST(testBuiltinRevertToOBSAtEnd);
|
||||
CPPUNIT_TEST(testRadialIntercept);
|
||||
CPPUNIT_TEST(testSWIFT8);
|
||||
CPPUNIT_TEST(testDMEIntercept);
|
||||
CPPUNIT_TEST(testFinalLegCourse);
|
||||
CPPUNIT_TEST(testCourseLegIntermediateWaypoint);
|
||||
CPPUNIT_TEST(testExceedFlyByMaxAngleTurn);
|
||||
CPPUNIT_TEST(testFlyOverMaxInterceptAngle);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
void setPositionAndStabilise(GPS* gps, const SGGeod& g);
|
||||
|
||||
GPS* setupStandardGPS(SGPropertyNode_ptr config = {},
|
||||
const std::string name = "gps", const int index = 0);
|
||||
void setupRouteManager();
|
||||
|
||||
public:
|
||||
// Set up function for each test.
|
||||
void setUp();
|
||||
|
||||
// Clean up after each test.
|
||||
void tearDown();
|
||||
|
||||
// The tests.
|
||||
void testBasic();
|
||||
void testNavRadioSlave();
|
||||
void testTurnAnticipation();
|
||||
void testOBSMode();
|
||||
void testDirectTo();
|
||||
void testLegMode();
|
||||
void testDirectToLegOnFlightplan();
|
||||
void testLongLeg();
|
||||
void testLongLegWestbound();
|
||||
void testOffsetFlight();
|
||||
void testOffcourseSequencing();
|
||||
void testOverflightSequencing();
|
||||
void testLegIntercept();
|
||||
void testDirectToLegOnFlightplanAndResumeBuiltin();
|
||||
void testBuiltinRevertToOBSAtEnd();
|
||||
void testRadialIntercept();
|
||||
void testSWIFT8();
|
||||
void testDMEIntercept();
|
||||
void testFinalLegCourse();
|
||||
void testCourseLegIntermediateWaypoint();
|
||||
void testExceedFlyByMaxAngleTurn();
|
||||
void testFlyOverMaxInterceptAngle();
|
||||
};
|
||||
|
||||
#endif // _FG_GPS_UNIT_TESTS_HXX
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _FG_HOLD_CTL_UNIT_TESTS_HXX
|
||||
#define _FG_HOLD_CTL_UNIT_TESTS_HXX
|
||||
|
||||
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <cppunit/TestFixture.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
class SGGeod;
|
||||
class GPS;
|
||||
|
||||
// The flight plan unit tests.
|
||||
class HoldControllerTests : public CppUnit::TestFixture
|
||||
{
|
||||
// Set up the test suite.
|
||||
CPPUNIT_TEST_SUITE(HoldControllerTests);
|
||||
CPPUNIT_TEST(testHoldEntryDirect);
|
||||
CPPUNIT_TEST(testHoldEntryTeardrop);
|
||||
CPPUNIT_TEST(testHoldEntryParallel);
|
||||
CPPUNIT_TEST(testLeftHoldEntryDirect);
|
||||
CPPUNIT_TEST(testLeftHoldEntryTeardrop);
|
||||
CPPUNIT_TEST(testLeftHoldEntryParallel);
|
||||
CPPUNIT_TEST(testHoldNotEntered);
|
||||
CPPUNIT_TEST(testHoldEntryOffCourse);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
void setPositionAndStabilise(const SGGeod& g);
|
||||
|
||||
GPS* setupStandardGPS(SGPropertyNode_ptr config = {},
|
||||
const std::string name = "gps", const int index = 0);
|
||||
void setupRouteManager();
|
||||
|
||||
public:
|
||||
// Set up function for each test.
|
||||
void setUp();
|
||||
|
||||
// Clean up after each test.
|
||||
void tearDown();
|
||||
|
||||
// The tests.
|
||||
void testHoldEntryDirect();
|
||||
void testHoldEntryTeardrop();
|
||||
void testHoldEntryParallel();
|
||||
void testLeftHoldEntryDirect();
|
||||
void testLeftHoldEntryTeardrop();
|
||||
void testLeftHoldEntryParallel();
|
||||
void testHoldNotEntered();
|
||||
void testHoldEntryOffCourse();
|
||||
private:
|
||||
GPS* m_gps = nullptr;
|
||||
SGPropertyNode_ptr m_gpsNode;
|
||||
};
|
||||
|
||||
#endif // _FG_HOLD_CTL_UNIT_TESTS_HXX
|
||||
@@ -0,0 +1,840 @@
|
||||
#include "test_navRadio.hxx"
|
||||
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
|
||||
#include "test_suite/FGTestApi/testGlobals.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);
|
||||
}
|
||||
}
|
||||
|
||||
std::string NavRadioTests::formatFrequency(double f)
|
||||
{
|
||||
char buf[16];
|
||||
::snprintf(buf, 16, "%3.2f", f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
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/nav", 6.0);
|
||||
node->setDoubleValue("frequencies/selected-mhz", 113.8);
|
||||
|
||||
SGGeod pos = SGGeod::fromDegFt(-3.352780, 55.499199, 20000);
|
||||
setPositionAndStabilise(r.get(), pos);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("operable"));
|
||||
CPPUNIT_ASSERT(node->getStringValue("nav-id") == "TLA");
|
||||
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
|
||||
}
|
||||
|
||||
static const struct {
|
||||
int nvType;
|
||||
double nvLat, nvLon, nvAlt, nvFreq, nvRnge, nvTwst;
|
||||
const string& nvIden;
|
||||
double onRose, atDstNM, atAltFt, atHdg, rSele;
|
||||
bool vOpnl, vToFlag;
|
||||
double vSigNorm, vSigTolr, vHdgDefl, vDeflTolr, vHdgNorm, vDefnTolr, xtkTolr;
|
||||
const string& tDesc;
|
||||
} CDITestRoll[] = {
|
||||
//
|
||||
// Test Items: Add test cases here:
|
||||
// nv<= fields are copied direct from nav dat => <= Rx pos wrt navaid, Radial =><= v- Values expected / tested => <= Line / Desc for Mesg =>
|
||||
//Type Lat Lon Alt Freq Rnge Twist Iden] onRose atNm atAlt atHdg rSele Op To sigN - Tolr Defl - Tolr DNrm -Tolr xtkTolr
|
||||
//
|
||||
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 25, 10.0, 4000, 200, 25, 1, 0, 1.0, 0.01, 0.0, 9.01, 0.0, 0.01, 50.0, "1: MCT EGCC On Radial" },
|
||||
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 25, 10.0, 4000, 200, 25, 1, 0, 1.0, 0.01, 0.0, 9.01, 0.0, 0.01, 50.0, "2: MCT EGCC On Again " },
|
||||
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 20, 20.0, 12000, 20, 25, 1, 0, 1.0, 0.01, 5.0, 0.1, 0.5, 0.01, 50.0, "3: MCT 5deg Off radial" },
|
||||
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 33, 30.0, 16000, 100, 25, 1, 0, 1.0, 0.01, -8.0, 0.1, -0.8, 0.01, 50.0, "4: MCT 8deg Off radial" },
|
||||
{ 3, 53.3, -2.26, 282, 113.55, 130, -5.0, "MCT", 38, 40.0, 16000, 280, 25, 1, 0, 1.0, 0.01, -10.0, 0.1, -1.0, 0.01, 50.0, "5: MCT >10 Off radial" },
|
||||
{ 3, -31.9, 115.95, 87, 113.70, 130, -2.0, "PH", 222, 20.0, 12000, 220, 42, 1, 1, 1.0, 0.01, 0.0, 0.01, 0.0, 0.01, 50.0, "6: PH Perth W.Aus On Radial"},
|
||||
{ 3, -31.9, 115.95, 87, 113.70, 130, -2.0, "PH", 225, 20.0, 18000, 220, 42, 1, 1, 1.0, 0.01, 3.0, 0.01, 0.3, 0.01, 50.0, "7: PH +3deg Off radial" }
|
||||
};
|
||||
|
||||
void NavRadioTests::callNavRadioCDI() {
|
||||
//
|
||||
//2021Ja15 set flag for newnavradio
|
||||
//
|
||||
fgSetBool("/instrumentation/use-new-navradio", true);
|
||||
// setup
|
||||
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/nav", 6.0);
|
||||
//
|
||||
int tale = sizeof(CDITestRoll) / sizeof(CDITestRoll[0]);
|
||||
for (int i = 0; (i < tale); i++) {
|
||||
// prep error message
|
||||
const string& itemDesc = " navradioCDI Item " + CDITestRoll[i].tDesc + " @ ";
|
||||
// Txmitting navaid
|
||||
node->setDoubleValue("frequencies/selected-mhz", CDITestRoll[i].nvFreq);
|
||||
node->setDoubleValue("radials/selected-deg", CDITestRoll[i].rSele);
|
||||
// tbd Filter on type as defined in nav dat
|
||||
//FGPositioned::TypeFilter f{FGPositioned::VOR};
|
||||
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::ILS, FGPositioned::LOC}};
|
||||
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(CDITestRoll[i].nvIden,
|
||||
SGGeod::fromDeg(CDITestRoll[i].nvLon, CDITestRoll[i].nvLat), &f));
|
||||
//
|
||||
// For VOR nav dat field 7: 'Twist' == Easterly rotation of Txmitter's 360 wrt True North c.f for Compass: 'Deviation West Rose is Best'
|
||||
// Rx posn is specified according to navaid's radials as printed on chart: True Bng = ( Radial on Rose + Twist ( Deviation ))
|
||||
// ( ftr: Both MCT -5 and PH -2 Are Negative Twists )
|
||||
SGGeod posWrtRadial = SGGeodesy::direct(nav->geod(), (CDITestRoll[i].onRose + CDITestRoll[i].nvTwst),
|
||||
(CDITestRoll[i].atDstNM * SG_NM_TO_METER));
|
||||
posWrtRadial.setElevationFt(CDITestRoll[i].atAltFt);
|
||||
setPositionAndStabilise(r.get(), posWrtRadial);
|
||||
// heading-deg property below means bearing to txmitter; calc copied from navradio.cxx !!!
|
||||
double bngToNavaid, az2, s;
|
||||
SGGeodesy::inverse(posWrtRadial, (nav->geod()), bngToNavaid, az2, s);
|
||||
// calc XTrack error
|
||||
double xtkE = sin((CDITestRoll[i].rSele - CDITestRoll[i].onRose) * SG_DEGREES_TO_RADIANS) * (CDITestRoll[i].atDstNM * SG_NM_TO_METER);
|
||||
// Verify expected vs Result
|
||||
string tMesg = itemDesc + "VOR type";
|
||||
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::VOR);
|
||||
tMesg = itemDesc + "Operable";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vOpnl, node->getBoolValue("operable"));
|
||||
tMesg = itemDesc + "TO Flag";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vToFlag, node->getBoolValue("to-flag"));
|
||||
tMesg = itemDesc + "FROM Flag";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vToFlag, !node->getBoolValue("from-flag"));
|
||||
//
|
||||
tMesg = itemDesc + "nav-id";
|
||||
CPPUNIT_ASSERT_MESSAGE(tMesg, node->getStringValue("nav-id") == CDITestRoll[i].nvIden);
|
||||
//tbd VOR seems to not set selected-mhz-fmt
|
||||
// Converting nvFreq to string results in trailing zeros
|
||||
tMesg = itemDesc + "selected-mhz-fmt";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, formatFrequency(CDITestRoll[i].nvFreq), string{node->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
// actual-deg means: bearing seen on intstrument's dial: actual == onRose
|
||||
tMesg = itemDesc + "actual-deg";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].onRose, node->getDoubleValue("radials/actual-deg"), CDITestRoll[i].vDefnTolr);
|
||||
// heading-deg means true bearing to navaid, not affected by plane's heading
|
||||
tMesg = itemDesc + "heading-deg";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, bngToNavaid, node->getDoubleValue("heading-deg"), 1);
|
||||
//
|
||||
tMesg = itemDesc + "Sig Norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), CDITestRoll[i].vSigTolr);
|
||||
tMesg = itemDesc + "needle defl";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vHdgDefl, node->getDoubleValue("heading-needle-deflection"), CDITestRoll[i].vDeflTolr);
|
||||
tMesg = itemDesc + "defl norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vHdgNorm, node->getDoubleValue("heading-needle-deflection-norm"), CDITestRoll[i].vDefnTolr);
|
||||
tMesg = itemDesc + "xTrack error";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xtkE, node->getDoubleValue("crosstrack-error-m"), CDITestRoll[i].xtkTolr);
|
||||
}
|
||||
}
|
||||
|
||||
void NavRadioTests::callNewNavRadioCDI()
|
||||
{
|
||||
//
|
||||
//2021Ja15 set flag for newnavradio
|
||||
//
|
||||
fgSetBool("/instrumentation/use-new-navradio", true);
|
||||
// setup
|
||||
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/nav", 6.0);
|
||||
//
|
||||
int tale = sizeof(CDITestRoll) / sizeof(CDITestRoll[0]);
|
||||
for (int i = 0; (i < tale); i++) {
|
||||
// prep error message
|
||||
const string& itemDesc = " navradioCDI Item " + CDITestRoll[i].tDesc + " @ ";
|
||||
// Txmitting navaid
|
||||
node->setDoubleValue("frequencies/selected-mhz", CDITestRoll[i].nvFreq);
|
||||
node->setDoubleValue("radials/selected-deg", CDITestRoll[i].rSele);
|
||||
// tbd Filter on type as defined in nav dat
|
||||
//FGPositioned::TypeFilter f{FGPositioned::VOR};
|
||||
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::ILS, FGPositioned::LOC}};
|
||||
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(CDITestRoll[i].nvIden,
|
||||
SGGeod::fromDeg(CDITestRoll[i].nvLon, CDITestRoll[i].nvLat), &f));
|
||||
//
|
||||
// For VOR nav dat field 7: 'Twist' == Easterly rotation of Txmitter's 360 wrt True North c.f for Compass: 'Deviation West Rose is Best'
|
||||
// Rx posn is specified according to navaid's radials as printed on chart: True Bng = ( Radial on Rose + Twist ( Deviation ))
|
||||
// ( ftr: Both MCT -5 and PH -2 Are Negative Twists )
|
||||
SGGeod posWrtRadial = SGGeodesy::direct(nav->geod(), (CDITestRoll[i].onRose + CDITestRoll[i].nvTwst),
|
||||
(CDITestRoll[i].atDstNM * SG_NM_TO_METER));
|
||||
posWrtRadial.setElevationFt(CDITestRoll[i].atAltFt);
|
||||
setPositionAndStabilise(r.get(), posWrtRadial);
|
||||
// heading-deg property below means bearing to txmitter; calc copied from navradio.cxx !!!
|
||||
double bngToNavaid, az2, s;
|
||||
SGGeodesy::inverse(posWrtRadial, (nav->geod()), bngToNavaid, az2, s);
|
||||
// calc XTrack error
|
||||
double xtkE = sin((CDITestRoll[i].rSele - CDITestRoll[i].onRose) * SG_DEGREES_TO_RADIANS) * (CDITestRoll[i].atDstNM * SG_NM_TO_METER);
|
||||
// Verify expected vs Result
|
||||
string tMesg = itemDesc + "VOR type";
|
||||
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::VOR);
|
||||
tMesg = itemDesc + "Operable";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vOpnl, node->getBoolValue("operable"));
|
||||
tMesg = itemDesc + "TO Flag";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vToFlag, node->getBoolValue("to-flag"));
|
||||
tMesg = itemDesc + "FROM Flag";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vToFlag, !node->getBoolValue("from-flag"));
|
||||
//
|
||||
tMesg = itemDesc + "nav-id";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, CDITestRoll[i].nvIden, string{node->getStringValue("nav-id")});
|
||||
|
||||
// Converting nvFreq to string results in trailing zeros
|
||||
tMesg = itemDesc + "selected-mhz-fmt";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, formatFrequency(CDITestRoll[i].nvFreq), string{node->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
|
||||
// actual-deg means: bearing seen on intstrument's dial: actual == onRose
|
||||
tMesg = itemDesc + "actual-deg";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].onRose, node->getDoubleValue("radials/actual-deg"), CDITestRoll[i].vDefnTolr);
|
||||
// heading-deg means true bearing to navaid, not affected by plane's heading
|
||||
tMesg = itemDesc + "heading-deg";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, bngToNavaid, node->getDoubleValue("heading-deg"), 1);
|
||||
//
|
||||
tMesg = itemDesc + "Sig Norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), CDITestRoll[i].vSigTolr);
|
||||
tMesg = itemDesc + "needle defl";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vHdgDefl, node->getDoubleValue("heading-needle-deflection"), CDITestRoll[i].vDeflTolr);
|
||||
tMesg = itemDesc + "defl norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, CDITestRoll[i].vHdgNorm, node->getDoubleValue("heading-needle-deflection-norm"), CDITestRoll[i].vDefnTolr);
|
||||
tMesg = itemDesc + "xTrack error";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xtkE, node->getDoubleValue("crosstrack-error-m"), CDITestRoll[i].xtkTolr);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct {
|
||||
int nvType;
|
||||
double nvLat, nvLon, nvAlt, nvFreq, nvRnge, nvTwst;
|
||||
const string& nvIden;
|
||||
double onRose, atDstNM, atAltFt, atHdg, rSele;
|
||||
bool vOpnl, vToFlag;
|
||||
double vSigNorm, vSigTolr, vHdgDefl, vDeflTolr, vHdgNorm, vDefnTolr, xtkTolr;
|
||||
const string& tDesc;
|
||||
|
||||
} ILSTestRoll[] = {
|
||||
//
|
||||
// Ref pilotscafe.com ILS width: 700ft wide at thrsh. WIthin range, sensed at +-35dg @ 10NM +-10dg @ 18NM
|
||||
//
|
||||
// Test Items: Add test cases here:
|
||||
// nv<= fields are copied direct from nav dat => <= Rx pos wrt navaid, Radial => <= v- Values expected / tested => <= Item - Desc =>
|
||||
//Typ Lat Lon Alt Freq Rnge TruHdng Iden] onRose atNm atAlt atHdg rSele Op To sigN - Tolr Defl - Tolr DNrm -Tolr xtkTol ]
|
||||
//
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 117.932, 2.5, 2500, 27, 297.932, 1, 1, 1.0, 0.01, 0.0, 0.01, 0.0, 0.01, 50.0, "1: ISFO On LOC"}, //
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 116.932, 6.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, -1.0, 0.10, -0.1, 0.01, 50.0, "2: ISFO -1 Deg"}, //
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 118.932, 6.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, 1.0, 0.01, -0.1, 0.01, 50.0, "3: ISFO +1 Deg"}, //
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 113.932, 3.0, 600, 27, 297.932, 1, 1, 1.0, 0.01, -3.0, 0.10, -0.1, 0.01, 50.0, "4: ISFO < MinDefl"}, //
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 121.932, 3.0, 600, 27, 297.932, 1, 1, 1.0, 0.01, 3.0, 0.01, -0.1, 0.01, 50.0, "5: ISFO > MzxDefl"}, //
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 297.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, 0.0, 0.01, 0.0, 0.01, 50.0, "6: ISFO BC On LOC"}, //
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 296.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, 1.0, 0.10, -0.1, 0.01, 50.0, "7: ISFO BC -1 Deg"}, //
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 298.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, -1.0, 0.01, -0.1, 0.01, 50.0, "8: ISFO BC +1 Deg"}, //
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 293.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, 3.0, 0.10, -0.1, 0.01, 50.0, "9: ISFO BC > MaxD"}, //
|
||||
{4, 37.626, -122.394, 8, 109.55, 18, 297.932, "ISFO", 301.932, 4.0, 1500, 27, 297.932, 1, 1, 1.0, 0.01, -3.0, 0.01, -0.1, 0.01, 50.0, "10: ISFO BC < MinD"} //
|
||||
};
|
||||
|
||||
void NavRadioTests::callNavRadioILS()
|
||||
{
|
||||
// set flag for newnavradio
|
||||
fgSetBool("/instrumentation/use-new-navradio", false);
|
||||
// setup
|
||||
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/nav", 6.0);
|
||||
//
|
||||
int tale = sizeof(ILSTestRoll) / sizeof(ILSTestRoll[0]);
|
||||
for (int i = 0; (i < tale); i++) {
|
||||
// prep error message
|
||||
const string& itemDesc = " navRadioILS Item " + ILSTestRoll[i].tDesc + " @ ";
|
||||
// Txmitting navaid
|
||||
node->setDoubleValue("frequencies/selected-mhz", ILSTestRoll[i].nvFreq);
|
||||
node->setDoubleValue("radials/selected-deg", ILSTestRoll[i].rSele);
|
||||
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::ILS, FGPositioned::LOC}};
|
||||
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(ILSTestRoll[i].nvIden,
|
||||
SGGeod::fromDeg(ILSTestRoll[i].nvLon, ILSTestRoll[i].nvLat), &f));
|
||||
SGGeod posWrtRadial = SGGeodesy::direct(nav->geod(), (ILSTestRoll[i].onRose), (ILSTestRoll[i].atDstNM * SG_NM_TO_METER));
|
||||
posWrtRadial.setElevationFt(ILSTestRoll[i].atAltFt);
|
||||
setPositionAndStabilise(r.get(), posWrtRadial);
|
||||
// heading-deg property below means bearing to txmitter; calc copied from navradio.cxx !!!
|
||||
double bngToNavaid, az2, s;
|
||||
SGGeodesy::inverse(posWrtRadial, (nav->geod()), bngToNavaid, az2, s);
|
||||
double xtkE = sin((ILSTestRoll[i].rSele - ILSTestRoll[i].onRose) * SG_DEGREES_TO_RADIANS) * (ILSTestRoll[i].atDstNM * SG_NM_TO_METER);
|
||||
//
|
||||
const double locWidth = nav->localizerWidth();
|
||||
// Expected Defl / Scaling is hokey because ILS width varies ??
|
||||
const double deflectionScale = 20.0 / locWidth; // 20 degrees is full VOR swing (-10 to +10 degrees)
|
||||
double xpecDefl = (ILSTestRoll[i].vHdgDefl * deflectionScale);
|
||||
xpecDefl = (xpecDefl > 10) ? 10 : xpecDefl;
|
||||
xpecDefl = (xpecDefl < -10) ? -10 : xpecDefl;
|
||||
//
|
||||
// Verify expected: Operational and To flags
|
||||
string tMesg = itemDesc + "ILS type";
|
||||
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::ILS);
|
||||
tMesg = itemDesc + "Operable";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].vOpnl, node->getBoolValue("operable"));
|
||||
tMesg = itemDesc + "TO Flag";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].vToFlag, node->getBoolValue("to-flag"));
|
||||
tMesg = itemDesc + "FROM Flag";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].vToFlag, !node->getBoolValue("from-flag"));
|
||||
//
|
||||
tMesg = itemDesc + "heading-deg";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, bngToNavaid, node->getDoubleValue("heading-deg"), 1);
|
||||
tMesg = itemDesc + "nav-id";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].nvIden, string{node->getStringValue("nav-id")});
|
||||
// Converting nvFreq to string results in trailing zeros
|
||||
tMesg = itemDesc + "selected-mhz-fmt";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, formatFrequency(ILSTestRoll[i].nvFreq), string{node->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
|
||||
// actual-deg means: bearing seen on intstrument's dial: actual == onRose
|
||||
tMesg = itemDesc + "actual-deg";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].onRose, node->getDoubleValue("radials/actual-deg"), ILSTestRoll[i].vDefnTolr);
|
||||
tMesg = itemDesc + "Sig Norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, ILSTestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), ILSTestRoll[i].vSigTolr);
|
||||
tMesg = itemDesc + "needle defl";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefl, node->getDoubleValue("heading-needle-deflection"), ILSTestRoll[i].vDeflTolr);
|
||||
tMesg = itemDesc + "defl norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, (xpecDefl * 0.1), node->getDoubleValue("heading-needle-deflection-norm"), ILSTestRoll[i].vDefnTolr);
|
||||
tMesg = itemDesc + "xTrack error";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xtkE, node->getDoubleValue("crosstrack-error-m"), ILSTestRoll[i].xtkTolr);
|
||||
}
|
||||
}
|
||||
|
||||
void NavRadioTests::callNewNavRadioILS()
|
||||
{
|
||||
// set flag for newnavradio
|
||||
fgSetBool("/instrumentation/use-new-navradio", true);
|
||||
// setup
|
||||
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/nav", 6.0);
|
||||
//
|
||||
int tale = sizeof(ILSTestRoll) / sizeof(ILSTestRoll[0]);
|
||||
for (int i = 0; (i < tale); i++) {
|
||||
// prep error message
|
||||
const string & itemDesc = "newNavRadioILS Item " + ILSTestRoll[i].tDesc + " @ ";
|
||||
// Txmitting navaid
|
||||
node->setDoubleValue("frequencies/selected-mhz", ILSTestRoll[i].nvFreq);
|
||||
node->setDoubleValue("radials/selected-deg", ILSTestRoll[i].rSele);
|
||||
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::ILS, FGPositioned::LOC}};
|
||||
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(ILSTestRoll[i].nvIden, \
|
||||
SGGeod::fromDeg( ILSTestRoll[i].nvLon, ILSTestRoll[i].nvLat), &f));
|
||||
SGGeod posWrtRadial = SGGeodesy::direct(nav->geod(), (ILSTestRoll[i].onRose ), (ILSTestRoll[i].atDstNM * SG_NM_TO_METER));
|
||||
posWrtRadial.setElevationFt(ILSTestRoll[i].atAltFt);
|
||||
setPositionAndStabilise(r.get(), posWrtRadial);
|
||||
// heading-deg property below means bearing to txmitter; calc copied from navradio.cxx !!!
|
||||
double bngToNavaid, az2, s;
|
||||
SGGeodesy::inverse(posWrtRadial, (nav->geod()), bngToNavaid, az2, s);
|
||||
double xtkE = sin( (ILSTestRoll[i].rSele - ILSTestRoll[i].onRose) * SG_DEGREES_TO_RADIANS) \
|
||||
* ( ILSTestRoll[i].atDstNM * SG_NM_TO_METER ) ;
|
||||
//
|
||||
const double locWidth = nav->localizerWidth();
|
||||
// Expected Defl / Scaling is hokey because ILS width varies ??
|
||||
const double deflectionScale = 20.0 / locWidth; // 20 degrees is full VOR swing (-10 to +10 degrees)
|
||||
double xpecDefl = (ILSTestRoll[i].vHdgDefl * deflectionScale);
|
||||
xpecDefl = ( xpecDefl > 10 ) ? 10 : xpecDefl;
|
||||
xpecDefl = ( xpecDefl < -10 ) ? -10 : xpecDefl;
|
||||
//
|
||||
// Verify expected: Operational and To flags
|
||||
string tMesg = itemDesc + "ILS type";
|
||||
CPPUNIT_ASSERT_MESSAGE( tMesg, nav->type() == FGPositioned::ILS);
|
||||
tMesg = itemDesc + "Operable";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].vOpnl, node->getBoolValue("operable"));
|
||||
tMesg = itemDesc + "TO Flag";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].vToFlag, node->getBoolValue("to-flag"));
|
||||
tMesg = itemDesc + "FROM Flag";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].vToFlag, !node->getBoolValue("from-flag"));
|
||||
//
|
||||
tMesg = itemDesc + "heading-deg";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, bngToNavaid, node->getDoubleValue("heading-deg"), 1);
|
||||
tMesg = itemDesc + "nav-id";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].nvIden, string{node->getStringValue("nav-id")});
|
||||
// Converting nvFreq to string results in trailing zeros
|
||||
tMesg = itemDesc + "selected-mhz-fmt";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, formatFrequency(ILSTestRoll[i].nvFreq), string{node->getStringValue("frequencies/selected-mhz-fmt")});
|
||||
// actual-deg means: bearing seen on intstrument's dial: actual == onRose
|
||||
tMesg = itemDesc + "actual-deg";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].onRose, node->getDoubleValue("radials/actual-deg"), ILSTestRoll[i].vDefnTolr);
|
||||
tMesg = itemDesc + "Sig Norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, ILSTestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), ILSTestRoll[i].vSigTolr);
|
||||
tMesg = itemDesc + "needle defl";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, xpecDefl, node->getDoubleValue("heading-needle-deflection"), ILSTestRoll[i].vDeflTolr);
|
||||
tMesg = itemDesc + "defl norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, (xpecDefl * 0.1 ), node->getDoubleValue("heading-needle-deflection-norm"), ILSTestRoll[i].vDefnTolr);
|
||||
tMesg = itemDesc + "xTrack error";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( tMesg, xtkE, node->getDoubleValue("crosstrack-error-m"), ILSTestRoll[i].xtkTolr);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct {
|
||||
int nvType;
|
||||
double nvLat, nvLon, nvAlt, nvFreq, nvRnge, nvAzim;
|
||||
const string& nvIden;
|
||||
double onRose, atDstNM, atAltFt, plusDeg, atHdg, rTruDeg;
|
||||
bool vInRnge, vFalse;
|
||||
double vSigNorm, vSigTolr, vGSDefl, vDeflTolr, vGSDefn, vDefnTolr;
|
||||
const string& tDesc;
|
||||
} GSTestRoll[] = {
|
||||
//
|
||||
// Test Items: Add test cases here:
|
||||
// nv<= fields are copied direct from nav dat => <= Rx pos wrt navaid, Radial => <= v- Values expected / tested => <= Item - Desc =>
|
||||
//Typ Lat Lon Alt Freq Rnge GSAzim Iden] onRose atNm atAlt or Deg atHdg TruDeg Rng Fls SgN - Tolr GSDefl-Tolr GSDefn-Tolr ]
|
||||
//
|
||||
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 8.0, 0, 0, 80.828, 260.857, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "1: EDDT 26R +0 deg" }, //
|
||||
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 4.0, 0, 0.50, 80.828, 260.857, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "2: EDDT 26R +0.5 d" }, //
|
||||
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 2.0, 0, -1.00, 80.828, 260.857, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "3: EDDT 26R -1 deg" }, //
|
||||
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 5.0, 0, 3.00, 80.828, 260.857, 1, 1, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "4: EDDT 26R +3.0 Fls"}, //
|
||||
{ 4, 52.563, 13.305, 101, 110.10, 10, 3.000, "ITLW", 117.932, 3.0, 0, +2.65, 80.828, 260.857, 1, 1, 1.0, 0.01,-1.75, 0.1, -0.5, 0.01, "5: EDDT 26R +3.5 Fls"}, //
|
||||
// { 4, 51.464, -0.439, 50, 109.50, 10, 3.000, "ILL", 89.690, 7.5, 2500, 0, 80.828, 269.690, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "6: EGLL 27L 2K5 7M5"}, // Fail: Rx finds IBB
|
||||
// { 4, 51.464, -0.439, 50, 109.50, 10, 3.000, "ILL", 89.690, 9.0, 3000, 0, 80.828, 269.690, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "7: EGLL 27L 3K0 9M0"}, //
|
||||
// { 4, 51.464, -0.439, 50, 109.50, 10, 3.000, "ILL", 89.690,17.5, 4000, 0, 80.828, 269.690, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "8: EGLL 27L 4K 17M5"}, //
|
||||
// { 4, 51.464, -0.439, 50, 109.50, 10, 3.000, "ILL", 89.690,25.0, 4000, 0, 80.828, 269.690, 1, 0, 1.0, 0.01, 0.0, 0.1, 0.0, 0.01, "9: EGLL 27L 4K 25M0"} //
|
||||
};
|
||||
|
||||
void NavRadioTests::callNavRadioGS() {
|
||||
//set flag for newnavradio
|
||||
fgSetBool("/instrumentation/use-new-navradio", false);
|
||||
// setup
|
||||
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/nav", 6.0);
|
||||
//
|
||||
// GS beam depth +-0.7deg; needle deflection -+3.5; gs-direct: Rxvr elevation from GS Txmitter level
|
||||
//
|
||||
const double halfBeam = 0.700;
|
||||
const double deflFact = 3.500;
|
||||
//
|
||||
int tale = sizeof(GSTestRoll) / sizeof(GSTestRoll[0]);
|
||||
for (int i = 0; (i < tale); i++) {
|
||||
// prep error message
|
||||
const string& itemDesc = " navRadioGS Item " + GSTestRoll[i].tDesc + " @ ";
|
||||
// Txmitting navaid
|
||||
node->setDoubleValue("frequencies/selected-mhz", GSTestRoll[i].nvFreq);
|
||||
node->setDoubleValue("radials/selected-deg", GSTestRoll[i].rTruDeg);
|
||||
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::GS, FGPositioned::LOC}};
|
||||
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(GSTestRoll[i].nvIden,
|
||||
SGGeod::fromDeg(GSTestRoll[i].nvLon, GSTestRoll[i].nvLat), &f));
|
||||
|
||||
// Check for proper nav type befor doing GS things
|
||||
string tMesg = itemDesc + "GS Type ?";
|
||||
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::GS);
|
||||
/////////////
|
||||
// derive the GS geometry in cartesian vectors, to match what navradio.cxx does
|
||||
SGGeod aboveGS = nav->geod();
|
||||
aboveGS.setElevationM(nav->geod().getElevationM() + 100);
|
||||
SGVec3d gsVerticalAxis = SGVec3d::fromGeod(aboveGS) - nav->cart();
|
||||
// intentionally different approach to what navradio uses
|
||||
gsVerticalAxis *= 0.01; // make it per meter, since we used 100m above
|
||||
// derive the baseline
|
||||
SGQuatd baseLineRot = SGQuatd::fromLonLat(nav->geod()) * SGQuatd::fromHeadAttBankDeg(GSTestRoll[i].atHdg, 0, 0);
|
||||
SGVec3d gsAltAxis = baseLineRot.backTransform(SGVec3d(1.0, 0.0, 0.0));
|
||||
const SGVec3d gsCart = nav->cart();
|
||||
//////////////////
|
||||
// expected deflection is calculated here if atAltFt is non-zero
|
||||
double xpecAzim, xpecDefl, xpecDefn;
|
||||
double bngToNavaid, az2, s;
|
||||
if (GSTestRoll[i].atAltFt == 0) {
|
||||
// Line item atAlt is zero so use degrees off GlideSlope for Rx position
|
||||
double gsAngleRad = (nav->glideSlopeAngleDeg() + GSTestRoll[i].plusDeg) * SG_DEGREES_TO_RADIANS;
|
||||
SGVec3d radioPos = gsCart;
|
||||
radioPos += (gsVerticalAxis * tan(gsAngleRad) * GSTestRoll[i].atDstNM * SG_NM_TO_METER);
|
||||
radioPos += (gsAltAxis * GSTestRoll[i].atDstNM * SG_NM_TO_METER);
|
||||
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
|
||||
xpecAzim = (GSTestRoll[i].nvAzim + GSTestRoll[i].plusDeg);
|
||||
} else {
|
||||
// Line item atAlt is non zero so use altitude for Rx position
|
||||
SGGeod p = SGGeodesy::direct(nav->geod(), GSTestRoll[i].atAltFt, GSTestRoll[i].atDstNM * SG_NM_TO_METER);
|
||||
p.setElevationFt(GSTestRoll[i].atAltFt);
|
||||
setPositionAndStabilise(r.get(), p);
|
||||
//tbd calc Rx Azim from Tx for altitude case
|
||||
SGGeodesy::inverse(p, nav->geod(), bngToNavaid, az2, s);
|
||||
xpecAzim = SG_RADIANS_TO_DEGREES * (atan((GSTestRoll[i].atAltFt * SG_FEET_TO_METER) / s));
|
||||
}
|
||||
//
|
||||
if (GSTestRoll[i].vFalse) {
|
||||
// rxFlse indicates false signal, use deflections manually entered in Item
|
||||
xpecDefl = GSTestRoll[i].vGSDefl;
|
||||
xpecDefn = GSTestRoll[i].vGSDefn;
|
||||
} else {
|
||||
if (GSTestRoll[i].atAltFt == 0) {
|
||||
// not rxFlse and atAltFt is zero : calculate needle deflections from Rx posn degrees wrt beam
|
||||
xpecDefn = 0 - GSTestRoll[i].plusDeg / halfBeam; // Plane high: needle below
|
||||
} else {
|
||||
// not rxFlse and atAltFt non zero : calculate needle deflections from Rx posn azimuth wrt beam
|
||||
xpecDefn = (GSTestRoll[i].nvAzim - az2) / halfBeam; // Plane high: needle below;
|
||||
}
|
||||
xpecDefn = (xpecDefn > 1) ? 1 : xpecDefn;
|
||||
xpecDefn = (xpecDefn < -1) ? -1 : xpecDefn;
|
||||
xpecDefl = xpecDefn * deflFact;
|
||||
}
|
||||
//
|
||||
// Verify expected:
|
||||
tMesg = itemDesc + "Operable";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, GSTestRoll[i].vInRnge, node->getBoolValue("operable"));
|
||||
tMesg = itemDesc + "nav-id";
|
||||
string dddbug = node->getStringValue("nav-id");
|
||||
CPPUNIT_ASSERT_MESSAGE(tMesg, node->getStringValue("nav-id") == GSTestRoll[i].nvIden);
|
||||
tMesg = itemDesc + "Sig Norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, GSTestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), GSTestRoll[i].vSigTolr);
|
||||
tMesg = itemDesc + "gs-direct";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecAzim, node->getDoubleValue("gs-direct-deg"), 1);
|
||||
tMesg = itemDesc + "needle defl";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefl, node->getDoubleValue("gs-needle-deflection"), GSTestRoll[i].vDeflTolr);
|
||||
tMesg = itemDesc + "defl norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefn, node->getDoubleValue("gs-needle-deflection-norm"), GSTestRoll[i].vDefnTolr);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
void NavRadioTests::callNewNavRadioGS()
|
||||
{
|
||||
//set flag for newnavradio
|
||||
fgSetBool("/instrumentation/use-new-navradio", true);
|
||||
// setup
|
||||
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/nav", 6.0);
|
||||
//
|
||||
// GS beam depth +-0.7deg; needle deflection -+3.5; gs-direct: Rxvr elevation from GS Txmitter level
|
||||
//
|
||||
const double halfBeam = 0.700;
|
||||
const double deflFact = 3.500;
|
||||
//
|
||||
int tale = sizeof(GSTestRoll) / sizeof(GSTestRoll[0]);
|
||||
for (int i = 0; (i < tale); i++) {
|
||||
// prep error message
|
||||
const string& itemDesc = "newnavRadioGS Item " + GSTestRoll[i].tDesc + " @ ";
|
||||
// Txmitting navaid
|
||||
node->setDoubleValue("frequencies/selected-mhz", GSTestRoll[i].nvFreq);
|
||||
node->setDoubleValue("radials/selected-deg", GSTestRoll[i].rTruDeg);
|
||||
FGPositioned::TypeFilter f{{FGPositioned::VOR, FGPositioned::GS, FGPositioned::LOC}};
|
||||
FGNavRecordRef nav = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent(GSTestRoll[i].nvIden,
|
||||
SGGeod::fromDeg(GSTestRoll[i].nvLon, GSTestRoll[i].nvLat), &f));
|
||||
|
||||
// Check for proper nav type befor doing GS things
|
||||
string tMesg = itemDesc + "GS Type ?";
|
||||
CPPUNIT_ASSERT_MESSAGE(tMesg, nav->type() == FGPositioned::GS);
|
||||
/////////////
|
||||
// derive the GS geometry in cartesian vectors, to match what navradio.cxx does
|
||||
SGGeod aboveGS = nav->geod();
|
||||
aboveGS.setElevationM(nav->geod().getElevationM() + 100);
|
||||
SGVec3d gsVerticalAxis = SGVec3d::fromGeod(aboveGS) - nav->cart();
|
||||
// intentionally different approach to what navradio uses
|
||||
gsVerticalAxis *= 0.01; // make it per meter, since we used 100m above
|
||||
// derive the baseline
|
||||
SGQuatd baseLineRot = SGQuatd::fromLonLat(nav->geod()) * SGQuatd::fromHeadAttBankDeg(GSTestRoll[i].atHdg, 0, 0);
|
||||
SGVec3d gsAltAxis = baseLineRot.backTransform(SGVec3d(1.0, 0.0, 0.0));
|
||||
const SGVec3d gsCart = nav->cart();
|
||||
//////////////////
|
||||
// expected deflection is calculated here if atAltFt is non-zero
|
||||
double xpecAzim, xpecDefl, xpecDefn;
|
||||
double bngToNavaid, az2, s;
|
||||
if (GSTestRoll[i].atAltFt == 0) {
|
||||
// Line item atAlt is zero so use degrees off GlideSlope for Rx position
|
||||
double gsAngleRad = (nav->glideSlopeAngleDeg() + GSTestRoll[i].plusDeg) * SG_DEGREES_TO_RADIANS;
|
||||
SGVec3d radioPos = gsCart;
|
||||
radioPos += (gsVerticalAxis * tan(gsAngleRad) * GSTestRoll[i].atDstNM * SG_NM_TO_METER);
|
||||
radioPos += (gsAltAxis * GSTestRoll[i].atDstNM * SG_NM_TO_METER);
|
||||
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
|
||||
xpecAzim = (GSTestRoll[i].nvAzim + GSTestRoll[i].plusDeg);
|
||||
} else {
|
||||
// Line item atAlt is non zero so use altitude for Rx position
|
||||
SGGeod p = SGGeodesy::direct(nav->geod(), GSTestRoll[i].atAltFt, GSTestRoll[i].atDstNM * SG_NM_TO_METER);
|
||||
p.setElevationFt(GSTestRoll[i].atAltFt);
|
||||
setPositionAndStabilise(r.get(), p);
|
||||
//tbd calc Rx Azim from Tx for altitude case
|
||||
SGGeodesy::inverse(p, nav->geod(), bngToNavaid, az2, s);
|
||||
xpecAzim = SG_RADIANS_TO_DEGREES * (atan((GSTestRoll[i].atAltFt * SG_FEET_TO_METER) / s));
|
||||
}
|
||||
//
|
||||
if (GSTestRoll[i].vFalse) {
|
||||
// rxFlse indicates false signal, use deflections manually entered in Item
|
||||
xpecDefl = GSTestRoll[i].vGSDefl;
|
||||
xpecDefn = GSTestRoll[i].vGSDefn;
|
||||
} else {
|
||||
if (GSTestRoll[i].atAltFt == 0) {
|
||||
// not rxFlse and atAltFt is zero : calculate needle deflections from Rx posn degrees wrt beam
|
||||
xpecDefn = 0 - GSTestRoll[i].plusDeg / halfBeam; // Plane high: needle below
|
||||
} else {
|
||||
// not rxFlse and atAltFt non zero : calculate needle deflections from Rx posn azimuth wrt beam
|
||||
xpecDefn = (GSTestRoll[i].nvAzim - az2) / halfBeam; // Plane high: needle below;
|
||||
}
|
||||
xpecDefn = (xpecDefn > 1) ? 1 : xpecDefn;
|
||||
xpecDefn = (xpecDefn < -1) ? -1 : xpecDefn;
|
||||
xpecDefl = xpecDefn * deflFact;
|
||||
}
|
||||
//
|
||||
// Verify expected:
|
||||
tMesg = itemDesc + "Operable";
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(tMesg, GSTestRoll[i].vInRnge, node->getBoolValue("operable"));
|
||||
tMesg = itemDesc + "nav-id";
|
||||
string dddbug = node->getStringValue("nav-id");
|
||||
CPPUNIT_ASSERT_MESSAGE(tMesg, node->getStringValue("nav-id") == GSTestRoll[i].nvIden);
|
||||
tMesg = itemDesc + "Sig Norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, GSTestRoll[i].vSigNorm, node->getDoubleValue("signal-quality-norm"), GSTestRoll[i].vSigTolr);
|
||||
tMesg = itemDesc + "gs-direct";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecAzim, node->getDoubleValue("gs-direct-deg"), 1);
|
||||
tMesg = itemDesc + "needle defl";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefl, node->getDoubleValue("gs-needle-deflection"), GSTestRoll[i].vDeflTolr);
|
||||
tMesg = itemDesc + "defl norm";
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(tMesg, xpecDefn, node->getDoubleValue("gs-needle-deflection-norm"), GSTestRoll[i].vDefnTolr);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
void NavRadioTests::testGS()
|
||||
{
|
||||
// radio setup
|
||||
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);
|
||||
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
|
||||
|
||||
// EDDT 28R
|
||||
FGPositioned::TypeFilter f{FGPositioned::GS};
|
||||
FGNavRecordRef gs = fgpositioned_cast<FGNavRecord>(
|
||||
FGPositioned::findClosestWithIdent("ITLW", SGGeod::fromDeg(13, 52), &f));
|
||||
CPPUNIT_ASSERT(gs->type() == FGPositioned::GS);
|
||||
node->setDoubleValue("frequencies/selected-mhz", 110.10);
|
||||
CPPUNIT_ASSERT(node->getStringValue("frequencies/selected-mhz-fmt") == "110.10");
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(gs->glideSlopeAngleDeg(), 3.0, 0.001);
|
||||
double gsAngleRad = gs->glideSlopeAngleDeg() * SG_DEGREES_TO_RADIANS;
|
||||
|
||||
/////////////
|
||||
// derive the GS geometry in cartesian vectors, to match what
|
||||
// navradio.cxx does
|
||||
SGGeod aboveGS = gs->geod();
|
||||
aboveGS.setElevationM(gs->geod().getElevationM() + 100.0);
|
||||
SGVec3d gsVerticalAxis = SGVec3d::fromGeod(aboveGS) - gs->cart();
|
||||
// intentionally different approach to what navradio uses
|
||||
|
||||
gsVerticalAxis *= 0.01; // make it per meter, since we used 100m above
|
||||
|
||||
// dervice the baseline
|
||||
SGQuatd baseLineRot = SGQuatd::fromLonLat(gs->geod()) * SGQuatd::fromHeadAttBankDeg(80.828, 0, 0);
|
||||
SGVec3d gsAltAxis = baseLineRot.backTransform(SGVec3d(1.0, 0.0, 0.0));
|
||||
|
||||
const SGVec3d gsCart = gs->cart();
|
||||
|
||||
//////////////////
|
||||
|
||||
SGVec3d radioPos = gsCart;
|
||||
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 8 * SG_NM_TO_METER);
|
||||
radioPos += (gsAltAxis * 8 * SG_NM_TO_METER);
|
||||
|
||||
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
|
||||
|
||||
CPPUNIT_ASSERT(node->getStringValue("nav-id") == "ITLW");
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, node->getDoubleValue("gs-direct-deg"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("gs-needle-deflection"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
|
||||
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
|
||||
|
||||
// 0.5 degree offset above
|
||||
gsAngleRad = (gs->glideSlopeAngleDeg() + 0.5) * SG_DEGREES_TO_RADIANS;
|
||||
radioPos = gsCart;
|
||||
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 4 * SG_NM_TO_METER);
|
||||
radioPos += (gsAltAxis * 4 * SG_NM_TO_METER);
|
||||
|
||||
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
|
||||
|
||||
CPPUNIT_ASSERT(node->getStringValue("nav-id") == "ITLW");
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(3.5, node->getDoubleValue("gs-direct-deg"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(-2.5, node->getDoubleValue("gs-needle-deflection"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.714, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
|
||||
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
|
||||
|
||||
// 1 degree below (danger!)
|
||||
gsAngleRad = (gs->glideSlopeAngleDeg() - 1.0) * SG_DEGREES_TO_RADIANS;
|
||||
radioPos = gsCart;
|
||||
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 2 * SG_NM_TO_METER);
|
||||
radioPos += (gsAltAxis * 2 * SG_NM_TO_METER);
|
||||
|
||||
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, node->getDoubleValue("gs-direct-deg"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(3.5, node->getDoubleValue("gs-needle-deflection"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
|
||||
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
|
||||
|
||||
// false course above, reversed
|
||||
gsAngleRad = (gs->glideSlopeAngleDeg() + 3.0) * SG_DEGREES_TO_RADIANS;
|
||||
radioPos = gsCart;
|
||||
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 5 * SG_NM_TO_METER);
|
||||
radioPos += (gsAltAxis * 5 * SG_NM_TO_METER);
|
||||
|
||||
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(6.0, node->getDoubleValue("gs-direct-deg"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("gs-needle-deflection"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
|
||||
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
|
||||
|
||||
// false course above, reversed, 0.35 offset below
|
||||
gsAngleRad = (gs->glideSlopeAngleDeg() + 2.65) * SG_DEGREES_TO_RADIANS;
|
||||
radioPos = gsCart;
|
||||
radioPos += (gsVerticalAxis * tan(gsAngleRad) * 3 * SG_NM_TO_METER);
|
||||
radioPos += (gsAltAxis * 3 * SG_NM_TO_METER);
|
||||
|
||||
setPositionAndStabilise(r.get(), SGGeod::fromCart(radioPos));
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, node->getDoubleValue("signal-quality-norm"), 0.01);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(5.65, node->getDoubleValue("gs-direct-deg"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.75, node->getDoubleValue("gs-needle-deflection"), 0.1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.5, node->getDoubleValue("gs-needle-deflection-norm"), 0.01);
|
||||
CPPUNIT_ASSERT(node->getBoolValue("gs-in-range"));
|
||||
}
|
||||
|
||||
void NavRadioTests::testILSFalseCourse()
|
||||
{
|
||||
|
||||
// also GS false lobes
|
||||
}
|
||||
|
||||
void NavRadioTests::testILSPaired()
|
||||
{
|
||||
// EGPH and countless more
|
||||
}
|
||||
|
||||
void NavRadioTests::testILSAdjacentPaired()
|
||||
{
|
||||
// eg KJFK
|
||||
}
|
||||
|
||||
void NavRadioTests::testGlideslopeLongDistance()
|
||||
{
|
||||
// radio setup
|
||||
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);
|
||||
globals->get_props()->setDoubleValue("systems/electrical/outputs/nav", 6.0);
|
||||
|
||||
// EGLL 27L
|
||||
FGPositioned::TypeFilter f{FGPositioned::GS};
|
||||
FGNavRecordRef gs = fgpositioned_cast<FGNavRecord>(
|
||||
FGPositioned::findClosestWithIdent("ILL", SGGeod::fromDeg(0, 51), &f));
|
||||
CPPUNIT_ASSERT(gs->type() == FGPositioned::GS);
|
||||
node->setDoubleValue("frequencies/selected-mhz", 109.50);
|
||||
CPPUNIT_ASSERT(node->getStringValue("frequencies/selected-mhz-fmt") == "109.50");
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(gs->glideSlopeAngleDeg(), 3.0, 0.001);
|
||||
double gsAngleRad = gs->glideSlopeAngleDeg() * SG_DEGREES_TO_RADIANS;
|
||||
|
||||
// standard approach (per charts)
|
||||
SGGeod p = SGGeodesy::direct(gs->geod(), 90, 7.5 * SG_NM_TO_METER);
|
||||
p.setElevationFt(2500);
|
||||
setPositionAndStabilise(r.get(), p);
|
||||
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("gs-in-range"));
|
||||
|
||||
// normal approach
|
||||
p = SGGeodesy::direct(gs->geod(), 90, 9 * SG_NM_TO_METER);
|
||||
p.setElevationFt(3000);
|
||||
setPositionAndStabilise(r.get(), p);
|
||||
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("gs-in-range"));
|
||||
|
||||
// in our current nav data, the GS range is defined as 10nm, so the gs-in-range
|
||||
// is false for these
|
||||
|
||||
// 4000 feet intercept
|
||||
p = SGGeodesy::direct(gs->geod(), 90, 12 * SG_NM_TO_METER);
|
||||
p.setElevationFt(4000);
|
||||
setPositionAndStabilise(r.get(), p);
|
||||
CPPUNIT_ASSERT_EQUAL(false, node->getBoolValue("gs-in-range"));
|
||||
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
|
||||
|
||||
// further back
|
||||
p = SGGeodesy::direct(gs->geod(), 90, 17.5 * SG_NM_TO_METER);
|
||||
p.setElevationFt(4000);
|
||||
setPositionAndStabilise(r.get(), p);
|
||||
CPPUNIT_ASSERT_EQUAL(false, node->getBoolValue("gs-in-range"));
|
||||
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
|
||||
|
||||
// really pushing it
|
||||
p = SGGeodesy::direct(gs->geod(), 90, 25 * SG_NM_TO_METER);
|
||||
p.setElevationFt(4000);
|
||||
setPositionAndStabilise(r.get(), p);
|
||||
CPPUNIT_ASSERT_EQUAL(false, node->getBoolValue("gs-in-range"));
|
||||
CPPUNIT_ASSERT_EQUAL(true, node->getBoolValue("in-range"));
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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(callNavRadioCDI);
|
||||
CPPUNIT_TEST(callNewNavRadioCDI);
|
||||
CPPUNIT_TEST(callNavRadioILS);
|
||||
CPPUNIT_TEST(callNewNavRadioILS);
|
||||
CPPUNIT_TEST(callNavRadioGS);
|
||||
CPPUNIT_TEST(callNewNavRadioGS);
|
||||
|
||||
CPPUNIT_TEST(testGS);
|
||||
CPPUNIT_TEST(testILSFalseCourse);
|
||||
CPPUNIT_TEST(testILSPaired);
|
||||
CPPUNIT_TEST(testILSAdjacentPaired);
|
||||
CPPUNIT_TEST(testGlideslopeLongDistance);
|
||||
|
||||
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();
|
||||
|
||||
std::string formatFrequency(double f);
|
||||
|
||||
// The tests.
|
||||
void testBasic();
|
||||
|
||||
void callNavRadioCDI();
|
||||
void callNewNavRadioCDI();
|
||||
void callNavRadioILS();
|
||||
void callNewNavRadioILS();
|
||||
void callNavRadioGS();
|
||||
void callNewNavRadioGS();
|
||||
|
||||
void testGS();
|
||||
void testILSFalseCourse();
|
||||
void testILSPaired();
|
||||
void testILSAdjacentPaired();
|
||||
void testGlideslopeLongDistance();
|
||||
};
|
||||
|
||||
|
||||
#endif // _FG_NAVRADIO_UNIT_TESTS_HXX
|
||||
@@ -0,0 +1,702 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 "test_rnav_procedures.hxx"
|
||||
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
|
||||
#include "test_suite/FGTestApi/testGlobals.hxx"
|
||||
#include "test_suite/FGTestApi/NavDataCache.hxx"
|
||||
#include "test_suite/FGTestApi/TestPilot.hxx"
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Navaids/NavDataCache.hxx>
|
||||
#include <Navaids/navrecord.hxx>
|
||||
#include <Navaids/navlist.hxx>
|
||||
#include <Navaids/FlightPlan.hxx>
|
||||
|
||||
#include <Instrumentation/gps.hxx>
|
||||
|
||||
#include <Autopilot/route_mgr.hxx>
|
||||
|
||||
using namespace flightgear;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
class TestFPDelegate : public FlightPlan::Delegate
|
||||
{
|
||||
public:
|
||||
FlightPlanRef thePlan;
|
||||
int sequenceCount = 0;
|
||||
|
||||
virtual ~TestFPDelegate()
|
||||
{
|
||||
}
|
||||
|
||||
void sequence() override
|
||||
{
|
||||
|
||||
++sequenceCount;
|
||||
int newIndex = thePlan->currentIndex() + 1;
|
||||
if (newIndex >= thePlan->numLegs()) {
|
||||
thePlan->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
thePlan->setCurrentIndex(newIndex);
|
||||
}
|
||||
|
||||
void currentWaypointChanged() override
|
||||
{
|
||||
}
|
||||
|
||||
void departureChanged() override
|
||||
{
|
||||
// mimic the default delegate, inserting the SID waypoints
|
||||
|
||||
// clear anything existing
|
||||
thePlan->clearWayptsWithFlag(WPT_DEPARTURE);
|
||||
|
||||
// insert waypt for the dpearture runway
|
||||
auto dr = new RunwayWaypt(thePlan->departureRunway(), thePlan);
|
||||
dr->setFlag(WPT_DEPARTURE);
|
||||
dr->setFlag(WPT_GENERATED);
|
||||
thePlan->insertWayptAtIndex(dr, 0);
|
||||
|
||||
if (thePlan->sid()) {
|
||||
WayptVec sidRoute;
|
||||
bool ok = thePlan->sid()->route(thePlan->departureRunway(), thePlan->sidTransition(), sidRoute);
|
||||
if (!ok)
|
||||
throw sg_exception("failed to route via SID");
|
||||
int insertIndex = 1;
|
||||
for (auto w : sidRoute) {
|
||||
w->setFlag(WPT_DEPARTURE);
|
||||
w->setFlag(WPT_GENERATED);
|
||||
thePlan->insertWayptAtIndex(w, insertIndex++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void arrivalChanged() override
|
||||
{
|
||||
// mimic the default delegate, inserting the STAR waypoints
|
||||
|
||||
// clear anything existing
|
||||
thePlan->clearWayptsWithFlag(WPT_ARRIVAL);
|
||||
|
||||
// insert waypt for the destination runway
|
||||
auto dr = new RunwayWaypt(thePlan->destinationRunway(), thePlan);
|
||||
dr->setFlag(WPT_ARRIVAL);
|
||||
dr->setFlag(WPT_GENERATED);
|
||||
auto leg = thePlan->insertWayptAtIndex(dr, -1);
|
||||
|
||||
if (thePlan->star()) {
|
||||
WayptVec starRoute;
|
||||
bool ok = thePlan->star()->route(thePlan->destinationRunway(), thePlan->starTransition(), starRoute);
|
||||
if (!ok)
|
||||
throw sg_exception("failed to route via STAR");
|
||||
int insertIndex = leg->index();
|
||||
for (auto w : starRoute) {
|
||||
w->setFlag(WPT_ARRIVAL);
|
||||
w->setFlag(WPT_GENERATED);
|
||||
thePlan->insertWayptAtIndex(w, insertIndex++);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // of anonymous namespace
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Set up function for each test.
|
||||
void RNAVProcedureTests::setUp()
|
||||
{
|
||||
FGTestApi::setUp::initTestGlobals("rnav-procedures");
|
||||
FGTestApi::setUp::initNavDataCache();
|
||||
|
||||
globals->get_subsystem_mgr()->bind();
|
||||
globals->get_subsystem_mgr()->init();
|
||||
|
||||
SGPath proceduresPath = SGPath::fromEnv("FG_PROCEDURES_PATH");
|
||||
if (proceduresPath.exists()) {
|
||||
globals->append_fg_scenery(proceduresPath);
|
||||
}
|
||||
|
||||
setupRouteManager();
|
||||
}
|
||||
|
||||
// Clean up after each test.
|
||||
void RNAVProcedureTests::tearDown()
|
||||
{
|
||||
FGTestApi::tearDown::shutdownTestGlobals();
|
||||
}
|
||||
|
||||
GPS* RNAVProcedureTests::setupStandardGPS(SGPropertyNode_ptr config,
|
||||
const std::string name, const int index)
|
||||
{
|
||||
SGPropertyNode_ptr configNode(config.valid() ? config
|
||||
: SGPropertyNode_ptr{new SGPropertyNode});
|
||||
configNode->setStringValue("name", name);
|
||||
configNode->setIntValue("number", index);
|
||||
|
||||
GPS* gps(new GPS(configNode));
|
||||
m_gps = gps;
|
||||
|
||||
m_gpsNode = globals->get_props()->getNode("instrumentation", true)->getChild(name, index, true);
|
||||
m_gpsNode->setBoolValue("serviceable", true);
|
||||
globals->get_props()->setDoubleValue("systems/electrical/outputs/gps", 6.0);
|
||||
|
||||
gps->bind();
|
||||
gps->init();
|
||||
|
||||
globals->add_subsystem("gps", gps, SGSubsystemMgr::POST_FDM);
|
||||
return gps;
|
||||
}
|
||||
|
||||
void RNAVProcedureTests::setupRouteManager()
|
||||
{
|
||||
auto rm = globals->add_new_subsystem<FGRouteMgr>(SGSubsystemMgr::GENERAL);
|
||||
rm->bind();
|
||||
rm->init();
|
||||
rm->postinit();
|
||||
}
|
||||
|
||||
void RNAVProcedureTests::setPositionAndStabilise(const SGGeod& g)
|
||||
{
|
||||
FGTestApi::setPosition(g);
|
||||
for (int i=0; i<60; ++i) {
|
||||
m_gps->update(0.015);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if 0
|
||||
void RNAVProcedureTests::testBasic()
|
||||
{
|
||||
setupStandardGPS();
|
||||
|
||||
FGPositioned::TypeFilter f{FGPositioned::VOR};
|
||||
auto bodrumVOR = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent("BDR", SGGeod::fromDeg(27.6, 37), &f));
|
||||
SGGeod p1 = SGGeodesy::direct(bodrumVOR->geod(), 45.0, 5.0 * SG_NM_TO_METER);
|
||||
|
||||
FGTestApi::setPositionAndStabilise(p1);
|
||||
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
void RNAVProcedureTests::testHeadingToAlt()
|
||||
{
|
||||
auto vhhh = FGAirport::findByIdent("VHHH");
|
||||
// FGTestApi::setUp::logPositionToKML("heading_to_alt");
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
auto fp = FlightPlan::create();
|
||||
|
||||
auto testDelegate = new TestFPDelegate;
|
||||
testDelegate->thePlan = fp;
|
||||
fp->addDelegate(testDelegate);
|
||||
|
||||
rm->setFlightPlan(fp);
|
||||
|
||||
// we don't have Nasal, but our delegate does the same work
|
||||
FGTestApi::setUp::populateFPWithNasal(fp, "VHHH", "25R", "EGLL", "27R", "HAZEL");
|
||||
|
||||
auto wp = new HeadingToAltitude(fp, "TO_4000", 270);
|
||||
wp->setAltitude(4000, RESTRICT_ABOVE);
|
||||
fp->insertWayptAtIndex(wp, 1); // between the runway WP and HAZEL
|
||||
|
||||
// FGTestApi::writeFlightPlanToKML(fp);
|
||||
|
||||
auto depRwy = fp->departureRunway();
|
||||
|
||||
setupStandardGPS();
|
||||
setPositionAndStabilise(fp->departureRunway()->pointOnCenterline(0.0));
|
||||
|
||||
fp->activate();
|
||||
m_gpsNode->setStringValue("command", "leg");
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(fp->currentIndex(), 0);
|
||||
|
||||
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
|
||||
pilot->resetAtPosition(depRwy->pointOnCenterline(0.0));
|
||||
pilot->setCourseTrue(depRwy->headingDeg());
|
||||
pilot->setSpeedKts(200);
|
||||
pilot->flyGPSCourse(m_gps);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(fp->currentIndex(), 0);
|
||||
|
||||
// check we sequence to the heading-to-alt wp
|
||||
bool ok = FGTestApi::runForTimeWithCheck(300.0, [fp] () {
|
||||
if (fp->currentIndex() == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
// leisurely climb out
|
||||
pilot->setVerticalFPM(1800);
|
||||
pilot->setTargetAltitudeFtMSL(8000);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"VHHH-25R"}, std::string{m_gpsNode->getStringValue("wp/wp[0]/ID")});
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"TO_4000"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")});
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, m_gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5);
|
||||
|
||||
// fly until we're turned to to heading
|
||||
ok = FGTestApi::runForTimeWithCheck(20, [pilot] () {
|
||||
return pilot->isOnHeading(270.);
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
// capture the position now
|
||||
SGGeod posAtHdgAltStart = globals->get_aircraft_position();
|
||||
FGTestApi::runForTime(40.0);
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, m_gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5);
|
||||
|
||||
const double crs = SGGeodesy::courseDeg(posAtHdgAltStart, globals->get_aircraft_position());
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(270.0, crs, 1.0);
|
||||
|
||||
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
|
||||
return (fp->currentIndex() == 2);
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(4000.0, globals->get_aircraft_position().getElevationFt(), 100.0);
|
||||
|
||||
FGTestApi::runForTime(40.0);
|
||||
}
|
||||
|
||||
// ugly version: the heading to hold will be very mis-aligned with
|
||||
// the course when the leg is sequenced. (more than 90 degrees turn(
|
||||
void RNAVProcedureTests::testUglyHeadingToAlt()
|
||||
{
|
||||
auto vhhh = FGAirport::findByIdent("VHHH");
|
||||
// FGTestApi::setUp::logPositionToKML("heading_to_alt_ugly");
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
auto fp = FlightPlan::create();
|
||||
|
||||
auto testDelegate = new TestFPDelegate;
|
||||
testDelegate->thePlan = fp;
|
||||
fp->addDelegate(testDelegate);
|
||||
|
||||
rm->setFlightPlan(fp);
|
||||
|
||||
// we don't have Nasal, but our delegate does the same work
|
||||
FGTestApi::setUp::populateFPWithNasal(fp, "VHHH", "07L", "EGLL", "27R", "HAZEL");
|
||||
|
||||
auto wp = new HeadingToAltitude(fp, "TO_4000", 210);
|
||||
wp->setAltitude(4000, RESTRICT_ABOVE);
|
||||
fp->insertWayptAtIndex(wp, 1); // between the runway WP and HAZEL
|
||||
|
||||
// FGTestApi::writeFlightPlanToKML(fp);
|
||||
|
||||
auto depRwy = fp->departureRunway();
|
||||
|
||||
setupStandardGPS();
|
||||
setPositionAndStabilise(fp->departureRunway()->pointOnCenterline(0.0));
|
||||
|
||||
fp->activate();
|
||||
m_gpsNode->setStringValue("command", "leg");
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(fp->currentIndex(), 0);
|
||||
|
||||
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
|
||||
pilot->resetAtPosition(depRwy->pointOnCenterline(0.0));
|
||||
pilot->setCourseTrue(depRwy->headingDeg());
|
||||
pilot->setSpeedKts(200);
|
||||
pilot->flyGPSCourse(m_gps);
|
||||
|
||||
// check we sequence to the heading-to-alt wp
|
||||
bool ok = FGTestApi::runForTimeWithCheck(240.0, [fp] () {
|
||||
if (fp->currentIndex() == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
// leisurely climb out
|
||||
pilot->setVerticalFPM(1800);
|
||||
pilot->setTargetAltitudeFtMSL(8000);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"VHHH-07L"}, std::string{m_gpsNode->getStringValue("wp/wp[0]/ID")});
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"TO_4000"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")});
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, m_gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5);
|
||||
|
||||
// fly until we're turned to to heading
|
||||
ok = FGTestApi::runForTimeWithCheck(120, [pilot] () {
|
||||
return pilot->isOnHeading(210.0);
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
// capture the position now
|
||||
SGGeod posAtHdgAltStart = globals->get_aircraft_position();
|
||||
FGTestApi::runForTime(40.0);
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, m_gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5);
|
||||
|
||||
const double crs = SGGeodesy::courseDeg(posAtHdgAltStart, globals->get_aircraft_position());
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(210.0, crs, 1.0);
|
||||
|
||||
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
|
||||
return (fp->currentIndex() == 2);
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(4000.0, globals->get_aircraft_position().getElevationFt(), 100.0);
|
||||
|
||||
FGTestApi::runForTime(40.0);
|
||||
}
|
||||
|
||||
|
||||
void RNAVProcedureTests::testEGPH_TLA6C()
|
||||
{
|
||||
|
||||
auto egph = FGAirport::findByIdent("EGPH");
|
||||
|
||||
auto sid = egph->findSIDWithIdent("TLA6C");
|
||||
// procedures not loaded, abandon test
|
||||
if (!sid)
|
||||
return;
|
||||
|
||||
// FGTestApi::setUp::logPositionToKML("procedure_egph_tla6c");
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
auto fp = FlightPlan::create();
|
||||
|
||||
auto testDelegate = new TestFPDelegate;
|
||||
testDelegate->thePlan = fp;
|
||||
fp->addDelegate(testDelegate);
|
||||
|
||||
rm->setFlightPlan(fp);
|
||||
FGTestApi::setUp::populateFPWithNasal(fp, "EGPH", "24", "EGLL", "27R", "DCS POL DTY");
|
||||
|
||||
fp->setSID(sid);
|
||||
|
||||
FGRunwayRef departureRunway = fp->departureRunway();
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"24"}, fp->legAtIndex(0)->waypoint()->source()->name());
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"UW"}, fp->legAtIndex(1)->waypoint()->ident());
|
||||
|
||||
auto d242Wpt = fp->legAtIndex(2)->waypoint();
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"D242H"}, d242Wpt->ident());
|
||||
CPPUNIT_ASSERT_EQUAL(true, d242Wpt->flag(WPT_OVERFLIGHT));
|
||||
|
||||
const auto wp3Ident = fp->legAtIndex(3)->waypoint()->ident();
|
||||
|
||||
// depeding which versino fo the procedures we loaded, we can find
|
||||
// one ID or the other
|
||||
CPPUNIT_ASSERT((wp3Ident == "D346T") || (wp3Ident == "D345T"));
|
||||
|
||||
// FGTestApi::writeFlightPlanToKML(fp);
|
||||
|
||||
CPPUNIT_ASSERT(rm->activate());
|
||||
|
||||
|
||||
setupStandardGPS();
|
||||
|
||||
FGTestApi::setPositionAndStabilise(departureRunway->threshold());
|
||||
m_gpsNode->setStringValue("command", "leg");
|
||||
|
||||
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
|
||||
pilot->resetAtPosition(globals->get_aircraft_position());
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(departureRunway->headingDeg(), m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
|
||||
pilot->setCourseTrue(m_gpsNode->getDoubleValue("wp/leg-true-course-deg"));
|
||||
pilot->setSpeedKts(220);
|
||||
pilot->flyGPSCourse(m_gps);
|
||||
|
||||
FGTestApi::runForTime(20.0);
|
||||
// check we're somewhere along the runway, on the centerline
|
||||
// and still on waypoint zero
|
||||
|
||||
bool ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
|
||||
if (fp->currentIndex() == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
// check what we sequenced to
|
||||
double elapsed = globals->get_sim_time_sec();
|
||||
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
|
||||
if (fp->currentIndex() == 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
|
||||
|
||||
elapsed = globals->get_sim_time_sec();
|
||||
|
||||
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
|
||||
if (fp->currentIndex() == 3) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
|
||||
|
||||
elapsed = globals->get_sim_time_sec();
|
||||
|
||||
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
|
||||
if (fp->currentIndex() == 4) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, m_gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05);
|
||||
|
||||
ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
|
||||
if (fp->currentIndex() == 5) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, fp->legAtIndex(5)->waypoint()->ident());
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")});
|
||||
}
|
||||
|
||||
void RNAVProcedureTests::testLFKC_AJO1R()
|
||||
{
|
||||
auto lfkc = FGAirport::findByIdent("LFKC");
|
||||
auto sid = lfkc->findSIDWithIdent("AJO1R");
|
||||
// procedures not loaded, abandon test
|
||||
if (!sid)
|
||||
return;
|
||||
|
||||
// FGTestApi::setUp::logPositionToKML("procedure_LFKC_AJO1R");
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
auto fp = FlightPlan::create();
|
||||
|
||||
auto testDelegate = new TestFPDelegate;
|
||||
testDelegate->thePlan = fp;
|
||||
fp->addDelegate(testDelegate);
|
||||
|
||||
rm->setFlightPlan(fp);
|
||||
FGTestApi::setUp::populateFPWithNasal(fp, "LFKC", "36", "EGLL", "27R", "");
|
||||
|
||||
fp->setSID(sid);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"BEBEV"}, fp->legAtIndex(4)->waypoint()->ident());
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"AJO"}, fp->legAtIndex(5)->waypoint()->ident());
|
||||
double d = fp->legAtIndex(5)->distanceAlongRoute();
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(72, d, 1.0); // ensure the route didn't blow up to 0,0
|
||||
|
||||
FGRunwayRef departureRunway = fp->departureRunway();
|
||||
// FGTestApi::writeFlightPlanToKML(fp);
|
||||
|
||||
CPPUNIT_ASSERT(rm->activate());
|
||||
|
||||
setupStandardGPS();
|
||||
|
||||
FGTestApi::setPositionAndStabilise(departureRunway->threshold());
|
||||
m_gpsNode->setStringValue("command", "leg");
|
||||
|
||||
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
|
||||
pilot->resetAtPosition(globals->get_aircraft_position());
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(departureRunway->headingDeg(), m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
|
||||
pilot->setCourseTrue(m_gpsNode->getDoubleValue("wp/leg-true-course-deg"));
|
||||
pilot->setSpeedKts(220);
|
||||
pilot->flyGPSCourse(m_gps);
|
||||
|
||||
FGTestApi::runForTime(20.0);
|
||||
// check we're somewhere along the runway, on the centerline
|
||||
// and still on waypoint zero
|
||||
|
||||
bool ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
|
||||
if (fp->currentIndex() == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
}
|
||||
|
||||
void RNAVProcedureTests::testTransitionsSID()
|
||||
{
|
||||
auto kjfk = FGAirport::findByIdent("kjfk");
|
||||
auto runway = kjfk->getRunwayByIdent("13L");
|
||||
|
||||
|
||||
auto sid = kjfk->selectSIDByTransition(runway, "CANDR");
|
||||
// procedures not loaded, abandon test
|
||||
if (!sid)
|
||||
return;
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
auto fp = FlightPlan::create();
|
||||
|
||||
auto testDelegate = new TestFPDelegate;
|
||||
testDelegate->thePlan = fp;
|
||||
fp->addDelegate(testDelegate);
|
||||
|
||||
rm->setFlightPlan(fp);
|
||||
FGTestApi::setUp::populateFPWithNasal(fp, "KJFK", "13L", "KCLE", "24R", "");
|
||||
|
||||
fp->setSID(sid);
|
||||
CPPUNIT_ASSERT_EQUAL(8, fp->numLegs());
|
||||
auto wp = fp->legAtIndex(6);
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"CANDR"}, wp->waypoint()->ident());
|
||||
CPPUNIT_ASSERT(rm->activate());
|
||||
}
|
||||
|
||||
void RNAVProcedureTests::testTransitionsSTAR()
|
||||
{
|
||||
auto kjfk = FGAirport::findByIdent("kjfk");
|
||||
auto runway = kjfk->getRunwayByIdent("22L");
|
||||
auto star = kjfk->selectSTARByTransition(runway, "SEY");
|
||||
// procedures not loaded, abandon test
|
||||
if (!star)
|
||||
return;
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
auto fp = FlightPlan::create();
|
||||
|
||||
auto testDelegate = new TestFPDelegate;
|
||||
testDelegate->thePlan = fp;
|
||||
fp->addDelegate(testDelegate);
|
||||
|
||||
rm->setFlightPlan(fp);
|
||||
FGTestApi::setUp::populateFPWithNasal(fp, "KBOS", "22R", "KJFK", "22L", "");
|
||||
|
||||
fp->setSTAR(star);
|
||||
CPPUNIT_ASSERT_EQUAL(9, fp->numLegs());
|
||||
auto wp = fp->legAtIndex(1);
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"SEY"}, wp->waypoint()->ident());
|
||||
CPPUNIT_ASSERT(rm->activate());
|
||||
}
|
||||
|
||||
void RNAVProcedureTests::testLEBL_LARP2F()
|
||||
{
|
||||
auto lebl = FGAirport::findByIdent("LEBL");
|
||||
auto sid = lebl->findSIDWithIdent("LARP1F.25L");
|
||||
// procedures not loaded, abandon test
|
||||
if (!sid)
|
||||
return;
|
||||
|
||||
FGTestApi::setUp::logPositionToKML("procedure_LEBL-LARP2F");
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
auto fp = FlightPlan::create();
|
||||
|
||||
auto testDelegate = new TestFPDelegate;
|
||||
testDelegate->thePlan = fp;
|
||||
fp->addDelegate(testDelegate);
|
||||
|
||||
rm->setFlightPlan(fp);
|
||||
FGTestApi::setUp::populateFPWithNasal(fp, "LEBL", "25L", "LEIB", "06", "");
|
||||
|
||||
fp->setSID(sid);
|
||||
// I don't know if this should pass or not!
|
||||
// If its a bug, wrap next line in CPPUNIT_ASSERT_ASSERTION_FAIL
|
||||
CPPUNIT_ASSERT_EQUAL(fp->legAtIndex(1)->waypoint()->position(), SGGeod::fromDeg(0, 0));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string{"LARPA"}, fp->legAtIndex(8)->waypoint()->ident());
|
||||
double d = fp->legAtIndex(8)->distanceAlongRoute();
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(46.6, d, 1.0); // ensure the route didn't blow up
|
||||
|
||||
FGTestApi::writeFlightPlanToKML(fp);
|
||||
|
||||
CPPUNIT_ASSERT(rm->activate());
|
||||
|
||||
FGRunwayRef departureRunway = fp->departureRunway();
|
||||
|
||||
setupStandardGPS();
|
||||
|
||||
FGTestApi::setPositionAndStabilise(departureRunway->threshold());
|
||||
m_gpsNode->setStringValue("command", "leg");
|
||||
|
||||
auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot);
|
||||
pilot->resetAtPosition(globals->get_aircraft_position());
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(departureRunway->headingDeg(), m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
|
||||
pilot->setCourseTrue(m_gpsNode->getDoubleValue("wp/leg-true-course-deg"));
|
||||
pilot->setSpeedKts(220);
|
||||
pilot->flyGPSCourse(m_gps);
|
||||
pilot->setTargetAltitudeFtMSL(8000);
|
||||
pilot->setVerticalFPM(1800);
|
||||
FGTestApi::runForTime(20.0);
|
||||
bool ok = FGTestApi::runForTimeWithCheck(180.0, [fp] () {
|
||||
if (fp->currentIndex() == 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
CPPUNIT_ASSERT(ok);
|
||||
|
||||
FGTestApi::runForTime(180.0);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(199, m_gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5);
|
||||
}
|
||||
|
||||
// This could probably be in a better place but this allows it to access TestDelegate.
|
||||
// Also it does relate to procedures as its a bug that only occurs with procedures
|
||||
void RNAVProcedureTests::testIndexOf()
|
||||
{
|
||||
auto egkk = FGAirport::findByIdent("EGKK");
|
||||
auto sid = egkk->findSIDWithIdent("SAM3P");
|
||||
// procedures not loaded, abandon test
|
||||
if (!sid)
|
||||
return;
|
||||
|
||||
auto rm = globals->get_subsystem<FGRouteMgr>();
|
||||
auto fp = FlightPlan::create();
|
||||
|
||||
auto testDelegate = new TestFPDelegate;
|
||||
testDelegate->thePlan = fp;
|
||||
fp->addDelegate(testDelegate);
|
||||
|
||||
rm->setFlightPlan(fp);
|
||||
FGTestApi::setUp::populateFPWithNasal(fp, "EGKK", "08R", "EGJJ", "27", "LELNA");
|
||||
|
||||
fp->setSID(sid);
|
||||
FGPositioned::TypeFilter f{FGPositioned::VOR};
|
||||
auto southamptonVOR = fgpositioned_cast<FGNavRecord>(FGPositioned::findClosestWithIdent("SAM", SGGeod::fromDeg(-1.25, 51.0), &f));
|
||||
auto SAM = fp->legAtIndex(6)->waypoint();
|
||||
CPPUNIT_ASSERT_EQUAL(southamptonVOR->ident(), SAM->ident());
|
||||
CPPUNIT_ASSERT_EQUAL(6, fp->findWayptIndex(southamptonVOR));
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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/>.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _FG_RNAV_PROCEDURE_UNIT_TESTS_HXX
|
||||
#define _FG_RNAV_PROCEDURE_UNIT_TESTS_HXX
|
||||
|
||||
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <cppunit/TestFixture.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <simgear/props/props.hxx>
|
||||
|
||||
class SGGeod;
|
||||
class GPS;
|
||||
|
||||
// The flight plan unit tests.
|
||||
class RNAVProcedureTests : public CppUnit::TestFixture
|
||||
{
|
||||
// Set up the test suite.
|
||||
CPPUNIT_TEST_SUITE(RNAVProcedureTests);
|
||||
|
||||
CPPUNIT_TEST(testEGPH_TLA6C);
|
||||
CPPUNIT_TEST(testHeadingToAlt);
|
||||
CPPUNIT_TEST(testUglyHeadingToAlt);
|
||||
CPPUNIT_TEST(testLFKC_AJO1R);
|
||||
CPPUNIT_TEST(testTransitionsSID);
|
||||
CPPUNIT_TEST(testTransitionsSTAR);
|
||||
CPPUNIT_TEST(testLEBL_LARP2F);
|
||||
CPPUNIT_TEST(testIndexOf);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
void setPositionAndStabilise(const SGGeod& g);
|
||||
|
||||
GPS* setupStandardGPS(SGPropertyNode_ptr config = {},
|
||||
const std::string name = "gps", const int index = 0);
|
||||
void setupRouteManager();
|
||||
|
||||
public:
|
||||
// Set up function for each test.
|
||||
void setUp();
|
||||
|
||||
// Clean up after each test.
|
||||
void tearDown();
|
||||
|
||||
// The tests.
|
||||
//void testBasic();
|
||||
|
||||
void testEGPH_TLA6C();
|
||||
void testHeadingToAlt();
|
||||
void testUglyHeadingToAlt();
|
||||
void testLFKC_AJO1R();
|
||||
void testTransitionsSID();
|
||||
void testTransitionsSTAR();
|
||||
void testLEBL_LARP2F();
|
||||
void testIndexOf();
|
||||
private:
|
||||
GPS* m_gps = nullptr;
|
||||
SGPropertyNode_ptr m_gpsNode;
|
||||
|
||||
};
|
||||
|
||||
#endif // _FG_RNAV_PROCEDURE_UNIT_TESTS_HXX
|
||||
@@ -0,0 +1,107 @@
|
||||
#include "test_transponder.hxx"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "test_suite/FGTestApi/NavDataCache.hxx"
|
||||
#include "test_suite/FGTestApi/testGlobals.hxx"
|
||||
|
||||
#include <Airports/airport.hxx>
|
||||
#include <Navaids/NavDataCache.hxx>
|
||||
|
||||
#include <Instrumentation/transponder.hxx>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/locale.hxx>
|
||||
|
||||
// Set up function for each test.
|
||||
void TransponderTests::setUp()
|
||||
{
|
||||
FGTestApi::setUp::initTestGlobals("transponder");
|
||||
FGTestApi::setUp::initNavDataCache();
|
||||
}
|
||||
|
||||
|
||||
// Clean up after each test.
|
||||
void TransponderTests::tearDown()
|
||||
{
|
||||
FGTestApi::tearDown::shutdownTestGlobals();
|
||||
}
|
||||
|
||||
SGSubsystemRef TransponderTests::setupStandardTransponder(const std::string& name, int index)
|
||||
{
|
||||
SGPropertyNode_ptr configNode(new SGPropertyNode);
|
||||
|
||||
globals->get_props()->setDoubleValue("systems/electrical/outputs/transponder", 10.0);
|
||||
|
||||
configNode->setStringValue("name", name);
|
||||
configNode->setIntValue("number", index);
|
||||
auto r = new Transponder(configNode);
|
||||
|
||||
r->bind();
|
||||
r->init();
|
||||
|
||||
globals->add_subsystem("transponder", r, SGSubsystemMgr::FDM);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void TransponderTests::testBasic()
|
||||
{
|
||||
SGPropertyNode* altitudeSource = fgGetNode("/instrumentation/altimeter", true);
|
||||
|
||||
auto t = setupStandardTransponder("transponder", 0);
|
||||
altitudeSource->setDoubleValue("mode-s-alt-ft", 1234.0);
|
||||
|
||||
|
||||
SGPropertyNode* xpdrNode = fgGetNode("/instrumentation/transponder[0]");
|
||||
xpdrNode->setIntValue("inputs/knob-mode", 5); // KNOB_ALT
|
||||
xpdrNode->setIntValue("inputs/mode", 2); // MODE S
|
||||
|
||||
xpdrNode->setIntValue("id-code", 4621);
|
||||
|
||||
t->update(1.0);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(4, xpdrNode->getIntValue("inputs/digit[3]"));
|
||||
CPPUNIT_ASSERT_EQUAL(6, xpdrNode->getIntValue("inputs/digit[2]"));
|
||||
CPPUNIT_ASSERT_EQUAL(2, xpdrNode->getIntValue("inputs/digit[1]"));
|
||||
CPPUNIT_ASSERT_EQUAL(1, xpdrNode->getIntValue("inputs/digit[0]"));
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(true, xpdrNode->getBoolValue("altitude-valid"));
|
||||
CPPUNIT_ASSERT_EQUAL(1234, xpdrNode->getIntValue("altitude"));
|
||||
|
||||
xpdrNode->setIntValue("inputs/digit[2]", 2);
|
||||
CPPUNIT_ASSERT_EQUAL(4221, xpdrNode->getIntValue("id-code"));
|
||||
|
||||
t->update(1.0);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(4221, xpdrNode->getIntValue("transmitted-id"));
|
||||
|
||||
xpdrNode->setBoolValue("inputs/ident-btn", true);
|
||||
CPPUNIT_ASSERT_EQUAL(true, xpdrNode->getBoolValue("ident"));
|
||||
xpdrNode->setBoolValue("inputs/ident-btn", false);
|
||||
|
||||
// remain on for now
|
||||
CPPUNIT_ASSERT_EQUAL(true, xpdrNode->getBoolValue("ident"));
|
||||
|
||||
FGTestApi::runForTime(20.0);
|
||||
CPPUNIT_ASSERT_EQUAL(false, xpdrNode->getBoolValue("ident"));
|
||||
|
||||
xpdrNode->setIntValue("inputs/knob-mode", 1); // KNOB_STANDBY
|
||||
}
|
||||
|
||||
void TransponderTests::testStandby()
|
||||
{
|
||||
SGPropertyNode* altitudeSource = fgGetNode("/instrumentation/altimeter", true);
|
||||
auto t = setupStandardTransponder("transponder", 0);
|
||||
altitudeSource->setDoubleValue("mode-s-alt-ft", 1234.0);
|
||||
|
||||
|
||||
SGPropertyNode* xpdrNode = fgGetNode("/instrumentation/transponder[0]");
|
||||
xpdrNode->setIntValue("inputs/knob-mode", 1); // KNOB_STANDBY
|
||||
xpdrNode->setIntValue("id-code", 4621);
|
||||
|
||||
t->update(1.0);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(4621, xpdrNode->getIntValue("id-code"));
|
||||
CPPUNIT_ASSERT_EQUAL(-9999, xpdrNode->getIntValue("transmitted-id"));
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
|
||||
class Transponder;
|
||||
class SGGeod;
|
||||
|
||||
// The flight plan unit tests.
|
||||
class TransponderTests : public CppUnit::TestFixture
|
||||
{
|
||||
// Set up the test suite.
|
||||
CPPUNIT_TEST_SUITE(TransponderTests);
|
||||
|
||||
CPPUNIT_TEST(testBasic);
|
||||
CPPUNIT_TEST(testStandby);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
SGSubsystemRef setupStandardTransponder(const std::string& name, int index);
|
||||
|
||||
public:
|
||||
// Set up function for each test.
|
||||
void setUp();
|
||||
|
||||
// Clean up after each test.
|
||||
void tearDown();
|
||||
|
||||
// The tests.
|
||||
void testBasic();
|
||||
void testStandby();
|
||||
};
|
||||
Reference in New Issue
Block a user