From 18c19885c627d4ab023a408d5d7ef426f581bf0c Mon Sep 17 00:00:00 2001 From: James Turner <zakalawe@mac.com> Date: Tue, 24 Sep 2019 11:29:05 +0100 Subject: [PATCH] Improvements to Nasal hold API Ensure changes from Nasal are picked up by delegates (such as GPS) when they are made. Add a route-manager test which sets and exits a hold using Nasal. As part of this, update the test API to make it easy to run Nasal from a test. --- src/Instrumentation/gps.cxx | 4 +- src/Navaids/FlightPlan.cxx | 57 ++++---- src/Navaids/FlightPlan.hxx | 9 ++ src/Scripting/NasalPositioned.cxx | 25 +++- test_suite/FGTestApi/globals.cxx | 25 ++++ test_suite/FGTestApi/globals.hxx | 9 +- .../unit_tests/Instrumentation/test_gps.cxx | 115 +++++++--------- .../Instrumentation/test_hold_controller.cxx | 32 ++--- .../unit_tests/Navaids/test_routeManager.cxx | 128 +++++++++++++++++- .../unit_tests/Navaids/test_routeManager.hxx | 4 +- 10 files changed, 294 insertions(+), 114 deletions(-) diff --git a/src/Instrumentation/gps.cxx b/src/Instrumentation/gps.cxx index b30dfe448..fb3b240ca 100644 --- a/src/Instrumentation/gps.cxx +++ b/src/Instrumentation/gps.cxx @@ -567,8 +567,8 @@ void GPS::currentWaypointChanged() int index = _route->currentIndex(), count = _route->numLegs(); if ((index < 0) || (index >= count)) { - _currentWaypt=NULL; - _prevWaypt=NULL; + _currentWaypt=nullptr; + _prevWaypt=nullptr; // no active leg on the route return; } diff --git a/src/Navaids/FlightPlan.cxx b/src/Navaids/FlightPlan.cxx index 9f27ffff8..61c94662a 100644 --- a/src/Navaids/FlightPlan.cxx +++ b/src/Navaids/FlightPlan.cxx @@ -1576,46 +1576,55 @@ double FlightPlan::Leg::distanceAlongRoute() const return _distanceAlongPath; } + +bool FlightPlan::Leg::convertWaypointToHold() +{ + const auto wty = _waypt->type(); + if (wty == "hold") { + return true; + } + + if ((wty != "basic") && (wty != "navaid")) { + SG_LOG(SG_INSTR, SG_WARN, "convertWaypointToHold: cannot convert waypt " << index() << " " << _waypt->ident() << " to a hold"); + return false; + } + + auto hold = new Hold(_waypt->position(), _waypt->ident(), const_cast<FlightPlan*>(_parent)); + + // default to a 1 minute hold with the radial being our arrival radial + hold->setHoldTime(60.0); + hold->setHoldRadial(_courseDeg); + _waypt = hold; // we drop our reference to the old waypoint + + markWaypointDirty(); + + return true; +} + bool FlightPlan::Leg::setHoldCount(int count) { if (count == 0) { _holdCount = count; return true; } - - const auto wty = _waypt->type(); - bool fireWaypointsChanged = false; - - if (wty != "hold") { - // upgrade to a hold if possible - if ((wty == "basic") || (wty == "navaid")) { - auto hold = new Hold(_waypt->position(), _waypt->ident(), const_cast<FlightPlan*>(_parent)); - - // default to a 1 minute hold with the radial being our arrival radial - hold->setHoldTime(60.0); - hold->setHoldRadial(_courseDeg); - fireWaypointsChanged = true; - _waypt = hold; // we drop our reference to the old waypoint - } else { - SG_LOG(SG_INSTR, SG_WARN, "setHoldCount: cannot convert waypt " << index() << " " << _waypt->ident() << " to a hold"); - return false; - } + + if (!convertWaypointToHold()) { + return false; } _holdCount = count; + markWaypointDirty(); + return true; +} - if (fireWaypointsChanged) { - SG_LOG(SG_INSTR, SG_INFO, "setHoldCount: changed waypoint type, notifying deleagtes the FP changed"); - + void FlightPlan::Leg::markWaypointDirty() + { auto fp = owner(); fp->lockDelegates(); fp->_waypointsChanged = true; fp->unlockDelegates(); } - return true; -} - int FlightPlan::Leg::holdCount() const { return _holdCount; diff --git a/src/Navaids/FlightPlan.hxx b/src/Navaids/FlightPlan.hxx index 7d9bd6696..aa5b145f5 100644 --- a/src/Navaids/FlightPlan.hxx +++ b/src/Navaids/FlightPlan.hxx @@ -104,6 +104,9 @@ public: int holdCount() const; + + bool convertWaypointToHold(); + unsigned int index() const; int altitudeFt() const; @@ -122,6 +125,12 @@ public: double distanceNm() const; double distanceAlongRoute() const; + /** + * helper function, if the waypoint is modified in some way, to + * notify the flightplan owning this leg, and hence any delegates + * obsering us + */ + void markWaypointDirty(); private: friend class FlightPlan; diff --git a/src/Scripting/NasalPositioned.cxx b/src/Scripting/NasalPositioned.cxx index afdc3843a..84bfffe2e 100644 --- a/src/Scripting/NasalPositioned.cxx +++ b/src/Scripting/NasalPositioned.cxx @@ -584,7 +584,7 @@ static const char* waypointCommonGetMember(naContext c, Waypt* wpt, const char* return ""; } -static void waypointCommonSetMember(naContext c, Waypt* wpt, const char* fieldName, naRef value) +static bool waypointCommonSetMember(naContext c, Waypt* wpt, const char* fieldName, naRef value) { if (!strcmp(fieldName, "wp_role")) { if (!naIsString(value)) naRuntimeError(c, "wp_role must be a string"); @@ -612,7 +612,12 @@ static void waypointCommonSetMember(naContext c, Waypt* wpt, const char* fieldNa hold->setRightHanded(); } } + } else { + // nothing changed + return false; } + + return true; } static const char* wayptGhostGetMember(naContext c, void* g, naRef field, naRef* out) @@ -744,13 +749,25 @@ static void legGhostSetMember(naContext c, void* g, naRef field, naRef value) { const char* fieldName = naStr_data(field); FlightPlan::Leg* leg = (FlightPlan::Leg*) g; - + + bool didChange = false; if (!strcmp(fieldName, "hold_count")) { const int count = static_cast<int>(value.num); // this may upgrade the waypoint to a hold - leg->setHoldCount(count); + if (!leg->setHoldCount(count)) + naRuntimeError(c, "unable to set hold on leg waypoint: maybe unsuitable waypt type?"); + } else if (!strcmp(fieldName, "hold_heading_radial_deg")) { + if (!leg->convertWaypointToHold()) + naRuntimeError(c, "couldn't convert leg waypoint into a hold"); + + // now we can call the base method + didChange = waypointCommonSetMember(c, leg->waypoint(), fieldName, value); } else { - waypointCommonSetMember(c, leg->waypoint(), fieldName, value); + didChange = waypointCommonSetMember(c, leg->waypoint(), fieldName, value); + } + + if (didChange) { + leg->markWaypointDirty(); } } diff --git a/test_suite/FGTestApi/globals.cxx b/test_suite/FGTestApi/globals.cxx index 5adfc665b..fcfa29499 100644 --- a/test_suite/FGTestApi/globals.cxx +++ b/test_suite/FGTestApi/globals.cxx @@ -230,6 +230,15 @@ void setPosition(const SGGeod& g) globals->get_props()->setDoubleValue("position/longitude-deg", g.getLongitudeDeg()); globals->get_props()->setDoubleValue("position/altitude-ft", g.getElevationFt()); } + +void setPositionAndStabilise(const SGGeod& g) +{ + setPosition(g); + for (int i=0; i<60; ++i) { + globals->get_subsystem_mgr()->update(0.015); + } +} + void runForTime(double t) { @@ -306,6 +315,22 @@ void writeGeodsToKML(const std::string &label, const flightgear::SGGeodVec& geod endCurrentLineString(); } +bool executeNasal(const std::string& code) +{ + auto nasal = globals->get_subsystem<FGNasalSys>(); + if (!nasal) { + throw sg_exception("Nasal not available"); + } + + std::string output, errors; + bool ok = nasal->parseAndRunWithOutput(code, output, errors); + if (!errors.empty()) { + SG_LOG(SG_NASAL, SG_ALERT, "Errors running Nasal:" << errors); + return false; + } + + return ok; +} namespace tearDown { diff --git a/test_suite/FGTestApi/globals.hxx b/test_suite/FGTestApi/globals.hxx index 50c51f44e..a243f6fcf 100644 --- a/test_suite/FGTestApi/globals.hxx +++ b/test_suite/FGTestApi/globals.hxx @@ -38,17 +38,22 @@ void populateFPWithNasal(flightgear::FlightPlanRef f, } // End of namespace setUp. +// helpers during tests + void setPosition(const SGGeod& g); - +void setPositionAndStabilise(const SGGeod& g); + void runForTime(double t); - using RunCheck = std::function<bool(void)>; +using RunCheck = std::function<bool(void)>; bool runForTimeWithCheck(double t, RunCheck check); void writeFlightPlanToKML(flightgear::FlightPlanRef fp); void writeGeodsToKML(const std::string &label, const flightgear::SGGeodVec& geods); + +bool executeNasal(const std::string& code); namespace tearDown { diff --git a/test_suite/unit_tests/Instrumentation/test_gps.cxx b/test_suite/unit_tests/Instrumentation/test_gps.cxx index c5e6bb384..95a36738e 100644 --- a/test_suite/unit_tests/Instrumentation/test_gps.cxx +++ b/test_suite/unit_tests/Instrumentation/test_gps.cxx @@ -82,14 +82,6 @@ void GPSTests::tearDown() FGTestApi::tearDown::shutdownTestGlobals(); } -void GPSTests::setPositionAndStabilise(GPS* gps, const SGGeod& g) -{ - FGTestApi::setPosition(g); - for (int i=0; i<60; ++i) { - gps->update(0.015); - } -} - GPS* GPSTests::setupStandardGPS(SGPropertyNode_ptr config, const std::string name, const int index) { @@ -123,13 +115,13 @@ void GPSTests::setupRouteManager() void GPSTests::testBasic() { - auto gps = setupStandardGPS(); + 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); - setPositionAndStabilise(gps, p1); + FGTestApi::setPositionAndStabilise(p1); auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); CPPUNIT_ASSERT_DOUBLES_EQUAL(p1.getLongitudeDeg(), gpsNode->getDoubleValue("indicated-longitude-deg"), 0.01); @@ -156,14 +148,14 @@ void GPSTests::testBasic() void GPSTests::testOBSMode() { - auto gps = setupStandardGPS(); + 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); - setPositionAndStabilise(gps, p1); - + FGTestApi::setPositionAndStabilise(p1); + auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); CPPUNIT_ASSERT_DOUBLES_EQUAL(p1.getLongitudeDeg(), gpsNode->getDoubleValue("indicated-longitude-deg"), 0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL(p1.getLatitudeDeg(), gpsNode->getDoubleValue("indicated-latitude-deg"), 0.01); @@ -182,7 +174,7 @@ void GPSTests::testOBSMode() // select OBS mode one it gpsNode->setStringValue("command", "obs"); - setPositionAndStabilise(gps, p1); + FGTestApi::setPositionAndStabilise(p1); CPPUNIT_ASSERT_EQUAL(std::string{"obs"}, std::string{gpsNode->getStringValue("mode")}); CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, gpsNode->getDoubleValue("wp/wp[1]/distance-nm"), 0.01); @@ -196,7 +188,7 @@ void GPSTests::testOBSMode() // off axis, angular SGGeod p2 = SGGeodesy::direct(bodrumVOR->geod(), 40.0, 4.0 * SG_NM_TO_METER); - setPositionAndStabilise(gps, p2); + FGTestApi::setPositionAndStabilise(p2); CPPUNIT_ASSERT_EQUAL(std::string{"obs"}, std::string{gpsNode->getStringValue("mode")}); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, gpsNode->getDoubleValue("wp/wp[1]/distance-nm"), 0.01); @@ -206,7 +198,7 @@ void GPSTests::testOBSMode() // off axis, perpendicular SGGeod p3 = SGGeodesy::direct(p1, 135, 0.5 * SG_NM_TO_METER); - setPositionAndStabilise(gps, p3); + FGTestApi::setPositionAndStabilise(p3); CPPUNIT_ASSERT_DOUBLES_EQUAL(225.0, gpsNode->getDoubleValue("desired-course-deg"), 0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05); @@ -214,15 +206,13 @@ void GPSTests::testOBSMode() void GPSTests::testDirectTo() { - auto gps = setupStandardGPS(); - - + 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(), 30.0, 16.0 * SG_NM_TO_METER); - setPositionAndStabilise(gps, p1); + FGTestApi::setPositionAndStabilise(p1); auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); @@ -251,16 +241,16 @@ void GPSTests::testDirectTo() // move off the direct-to line, check everything works SGGeod p2 = SGGeodesy::direct(bodrumVOR->geod(), 35.0, 12.0 * SG_NM_TO_METER); - setPositionAndStabilise(gps, p2); + FGTestApi::setPositionAndStabilise(p2); CPPUNIT_ASSERT_DOUBLES_EQUAL(5, gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.1); SGGeod p3 = SGGeodesy::direct(p1, 210, 6.0 * SG_NM_TO_METER); SGGeod xtk = SGGeodesy::direct(p3, 120, 0.8 * SG_NM_TO_METER); - setPositionAndStabilise(gps, xtk); + FGTestApi::setPositionAndStabilise(xtk); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.8, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.1); SGGeod xtk2 = SGGeodesy::direct(p3, 120, -1.8 * SG_NM_TO_METER); - setPositionAndStabilise(gps, xtk2); + FGTestApi::setPositionAndStabilise(xtk2); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.8, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.1); } @@ -288,9 +278,9 @@ void GPSTests::testLegMode() CPPUNIT_ASSERT(rm->activate()); fp->addDelegate(testDelegate); - auto gps = setupStandardGPS(); + setupStandardGPS(); - setPositionAndStabilise(gps, fp->departureRunway()->pointOnCenterline(0.0)); + FGTestApi::setPositionAndStabilise(fp->departureRunway()->pointOnCenterline(0.0)); auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); gpsNode->setBoolValue("config/delegate-sequencing", true); @@ -320,7 +310,7 @@ void GPSTests::testLegMode() SGGeod nikPos = fp->currentLeg()->waypoint()->position(); SGGeod p2 = SGGeodesy::direct(nikPos, 90, 5 * SG_NM_TO_METER); // due east of NIK - setPositionAndStabilise(gps, p2); + FGTestApi::setPositionAndStabilise(p2); CPPUNIT_ASSERT_DOUBLES_EQUAL(270, gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5); @@ -356,11 +346,11 @@ void GPSTests::testLegMode() double course = SGGeodesy::courseDeg(coaPos, doverPos); double revCourse = SGGeodesy::courseDeg(doverPos, coaPos); - setPositionAndStabilise(gps, coaPos); + FGTestApi::setPositionAndStabilise(coaPos); CPPUNIT_ASSERT_DOUBLES_EQUAL(course, gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5); SGGeod off1 = SGGeodesy::direct(doverPos, revCourse - 5.0, 16 * SG_NM_TO_METER); - setPositionAndStabilise(gps, off1); + FGTestApi::setPositionAndStabilise(off1); double courseToDover = SGGeodesy::courseDeg(off1, doverPos); CPPUNIT_ASSERT_DOUBLES_EQUAL(courseToDover, gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5); @@ -370,7 +360,7 @@ void GPSTests::testLegMode() CPPUNIT_ASSERT_EQUAL(false, gpsNode->getBoolValue("wp/wp[1]/from-flag")); SGGeod off2 = SGGeodesy::direct(doverPos, revCourse + 8.0, 40 * SG_NM_TO_METER); - setPositionAndStabilise(gps, off2); + FGTestApi::setPositionAndStabilise(off2); courseToDover = SGGeodesy::courseDeg(off2, doverPos); CPPUNIT_ASSERT_DOUBLES_EQUAL(courseToDover, gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5); @@ -382,20 +372,20 @@ void GPSTests::testLegMode() SGGeod alongTrack = SGGeodesy::direct(coaPos, course, 20 * SG_NM_TO_METER); SGGeod offTrack = SGGeodesy::direct(alongTrack, course + 90, SG_NM_TO_METER * 0.7); - setPositionAndStabilise(gps, offTrack); + FGTestApi::setPositionAndStabilise(offTrack); courseToDover = SGGeodesy::courseDeg(offTrack, doverPos); CPPUNIT_ASSERT_DOUBLES_EQUAL(courseToDover, gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(-0.7, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.1); SGGeod offTrack2 = SGGeodesy::direct(alongTrack, courseToDover - 90, SG_NM_TO_METER * 3.4); - setPositionAndStabilise(gps, offTrack2); + FGTestApi::setPositionAndStabilise(offTrack2); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.4, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.1); // check cross track very close to COA SGGeod alongTrack11 = SGGeodesy::direct(coaPos, course, 0.3); SGGeod offTrack25 = SGGeodesy::direct(alongTrack11, course + 90, SG_NM_TO_METER * -3.2); - setPositionAndStabilise(gps, offTrack25); + FGTestApi::setPositionAndStabilise(offTrack25); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.2, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(course, gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.1); @@ -403,14 +393,14 @@ void GPSTests::testLegMode() double distanceCOA_DVR = SGGeodesy::distanceM(coaPos, doverPos); SGGeod alongTrack2 = SGGeodesy::direct(coaPos, course, distanceCOA_DVR - 0.3); SGGeod offTrack3 = SGGeodesy::direct(alongTrack2, course + 90, SG_NM_TO_METER * 1.6); - setPositionAndStabilise(gps, offTrack3); + FGTestApi::setPositionAndStabilise(offTrack3); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.6, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(revCourse + 180.0, gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.1); // check cross track in the middle SGGeod alongTrack3 = SGGeodesy::direct(coaPos, course, distanceCOA_DVR * 0.52); SGGeod offTrack4 = SGGeodesy::direct(alongTrack3, course + 90, SG_NM_TO_METER * 15.6); - setPositionAndStabilise(gps, offTrack4); + FGTestApi::setPositionAndStabilise(offTrack4); CPPUNIT_ASSERT_DOUBLES_EQUAL(-15.6, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(261.55, gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.1); } @@ -437,14 +427,14 @@ void GPSTests::testBuiltinRevertToOBSAtEnd() fp->addDelegate(testDelegate); auto gps = setupStandardGPS(); - setPositionAndStabilise(gps, fp->departureRunway()->pointOnCenterline(0.0)); + FGTestApi::setPositionAndStabilise(fp->departureRunway()->pointOnCenterline(0.0)); auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); gpsNode->setStringValue("command", "leg"); // move to point 18.0 nm after BDN auto posNearApproach = fp->pointAlongRoute(5, 18.0); - setPositionAndStabilise(gps, posNearApproach); + FGTestApi::setPositionAndStabilise(posNearApproach); fp->setCurrentIndex(6); auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot); @@ -482,10 +472,9 @@ void GPSTests::testDirectToLegOnFlightplan() CPPUNIT_ASSERT(rm->activate()); fp->addDelegate(testDelegate); - auto gps = setupStandardGPS(); + setupStandardGPS(); - - setPositionAndStabilise(gps, fp->departureRunway()->pointOnCenterline(0.0)); + FGTestApi::setPositionAndStabilise(fp->departureRunway()->pointOnCenterline(0.0)); auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); gpsNode->setBoolValue("config/delegate-sequencing", true); @@ -499,7 +488,7 @@ void GPSTests::testDirectToLegOnFlightplan() // initiate a direct to SGGeod p2 = fp->departureRunway()->pointOnCenterline(5.0* SG_NM_TO_METER); - setPositionAndStabilise(gps, p2); + FGTestApi::setPositionAndStabilise(p2); auto doverVOR = fp->legAtIndex(3)->waypoint()->source(); @@ -545,7 +534,7 @@ void GPSTests::testDirectToLegOnFlightplanAndResumeBuiltin() CPPUNIT_ASSERT(rm->activate()); auto gps = setupStandardGPS(); - setPositionAndStabilise(gps, fp->departureRunway()->pointOnCenterline(0.0)); + FGTestApi::setPositionAndStabilise(fp->departureRunway()->pointOnCenterline(0.0)); auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); gpsNode->setStringValue("command", "leg"); @@ -558,7 +547,7 @@ void GPSTests::testDirectToLegOnFlightplanAndResumeBuiltin() // initiate a direct to SGGeod p2 = fp->departureRunway()->pointOnCenterline(5.0* SG_NM_TO_METER); - setPositionAndStabilise(gps, p2); + FGTestApi::setPositionAndStabilise(p2); auto doverVOR = fp->legAtIndex(3)->waypoint()->source(); @@ -576,7 +565,7 @@ void GPSTests::testDirectToLegOnFlightplanAndResumeBuiltin() // note this behaviour is from the old C++ sequencing SGGeod posNearDover = SGGeodesy::direct(p2, bearingToDover, (distanceToDover - 8.0) * SG_NM_TO_METER); - setPositionAndStabilise(gps, posNearDover); + FGTestApi::setPositionAndStabilise(posNearDover); auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot); pilot->resetAtPosition(posNearDover); @@ -608,7 +597,7 @@ void GPSTests::testLongLeg() CPPUNIT_ASSERT(rm->activate()); fp->addDelegate(testDelegate); - auto gps = setupStandardGPS(); + setupStandardGPS(); auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); gpsNode->setBoolValue("config/delegate-sequencing", true); gpsNode->setStringValue("command", "leg"); @@ -625,7 +614,7 @@ void GPSTests::testLongLeg() fp->setCurrentIndex(2); // initial course at VNY - setPositionAndStabilise(gps, vanNuysVOR->geod()); + FGTestApi::setPositionAndStabilise(vanNuysVOR->geod()); const double initialCourse = SGGeodesy::courseDeg(vanNuysVOR->geod(), teterboroVOR->geod()); const double distanceM = SGGeodesy::distanceM(vanNuysVOR->geod(), teterboroVOR->geod()); @@ -640,7 +629,7 @@ void GPSTests::testLongLeg() // part of the way, check enroute leg course const SGGeod onTheWay = SGGeodesy::direct(vanNuysVOR->geod(), initialCourse, distanceM * 0.7); - setPositionAndStabilise(gps, onTheWay); + FGTestApi::setPositionAndStabilise(onTheWay); const double courseNow = SGGeodesy::courseDeg(onTheWay, teterboroVOR->geod()); const double distNow = SGGeodesy::distanceM(onTheWay, teterboroVOR->geod()); @@ -656,7 +645,7 @@ void GPSTests::testLongLeg() // check a seriously abeam point, we got far of course // to the right of the course, i.e desired track is to our left, so -ve const SGGeod offTheWay = SGGeodesy::direct(onTheWay, courseNow + 90, SG_NM_TO_METER * 13.5); - setPositionAndStabilise(gps, offTheWay); + FGTestApi::setPositionAndStabilise(offTheWay); const double courseFromOff1 = SGGeodesy::courseDeg(offTheWay, teterboroVOR->geod()); const double dist2 = SGGeodesy::distanceM(offTheWay, teterboroVOR->geod()); @@ -673,7 +662,7 @@ void GPSTests::testLongLeg() const SGGeod onTheWay2 = SGGeodesy::direct(vanNuysVOR->geod(), initialCourse, distanceM * 0.92); const double courseOn2 = SGGeodesy::courseDeg(onTheWay2, teterboroVOR->geod()); const SGGeod off2 = SGGeodesy::direct(onTheWay2, courseOn2 - 90, SG_NM_TO_METER * 31.2); - setPositionAndStabilise(gps, off2); + FGTestApi::setPositionAndStabilise(off2); const double courseFromOff2 = SGGeodesy::courseDeg(off2, teterboroVOR->geod()); const double dist3 = SGGeodesy::distanceM(off2, teterboroVOR->geod()); @@ -701,7 +690,7 @@ void GPSTests::testLongLegWestbound() CPPUNIT_ASSERT(rm->activate()); fp->addDelegate(testDelegate); - auto gps = setupStandardGPS(); + setupStandardGPS(); auto gpsNode = globals->get_props()->getNode("instrumentation/gps"); gpsNode->setBoolValue("config/delegate-sequencing", true); @@ -717,7 +706,7 @@ void GPSTests::testLongLegWestbound() fp->setCurrentIndex(2); // initial course at VNY - setPositionAndStabilise(gps, volloVOR->geod()); + FGTestApi::setPositionAndStabilise(volloVOR->geod()); const double initialCourse = SGGeodesy::courseDeg(volloVOR->geod(), gaktu->geod()); const double distanceM = SGGeodesy::distanceM(volloVOR->geod(), gaktu->geod()); @@ -732,7 +721,7 @@ void GPSTests::testLongLegWestbound() // part of the way, check enroute leg course const SGGeod onTheWay = SGGeodesy::direct(volloVOR->geod(), initialCourse, distanceM * 0.4); - setPositionAndStabilise(gps, onTheWay); + FGTestApi::setPositionAndStabilise(onTheWay); const double courseNow = SGGeodesy::courseDeg(onTheWay, gaktu->geod()); const double distNow = SGGeodesy::distanceM(onTheWay, gaktu->geod()); @@ -748,7 +737,7 @@ void GPSTests::testLongLegWestbound() // check a seriously abeam point, we got far of course // to the right of the course, i.e desired track is to our left, so -ve const SGGeod offTheWay = SGGeodesy::direct(onTheWay, courseNow + 90, SG_NM_TO_METER * 18.6); - setPositionAndStabilise(gps, offTheWay); + FGTestApi::setPositionAndStabilise(offTheWay); const double courseFromOff1 = SGGeodesy::courseDeg(offTheWay, gaktu->geod()); const double dist2 = SGGeodesy::distanceM(offTheWay, gaktu->geod()); @@ -797,7 +786,7 @@ void GPSTests::testOverflightSequencing() // enroute to NELSON VOR fp->setCurrentIndex(2); SGGeod pos = fp->pointAlongRoute(2, -5.0); - setPositionAndStabilise(gps, pos); + FGTestApi::setPositionAndStabilise(pos); auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot); pilot->resetAtPosition(pos); @@ -831,7 +820,7 @@ void GPSTests::testOverflightSequencing() // point a bit before MAMOD SGGeod pos2 = fp->pointAlongRoute(5, -5.0); - setPositionAndStabilise(gps, pos2); + FGTestApi::setPositionAndStabilise(pos2); fp->setCurrentIndex(5); auto mamod = fp->legAtIndex(5)->waypoint()->source(); @@ -893,7 +882,7 @@ void GPSTests::testOffcourseSequencing() SGGeod pos = fp->pointAlongRoute(2, -5.0); pos = SGGeodesy::direct(pos, 180, 1.5 * SG_NM_TO_METER); - setPositionAndStabilise(gps, pos); + FGTestApi::setPositionAndStabilise(pos); auto nelsonVOR = fp->legAtIndex(2)->waypoint()->source(); @@ -952,7 +941,7 @@ void GPSTests::testOffcourseSequencing() pos2 = SGGeodesy::direct(pos2, 180, 0.8 * SG_NM_TO_METER); fp->setCurrentIndex(5); - setPositionAndStabilise(gps, pos2); + FGTestApi::setPositionAndStabilise(pos2); // start facing the right way pilot->setCourseTrue(gpsNode->getDoubleValue("wp/leg-true-course-deg")); pilot->flyHeading(gpsNode->getDoubleValue("wp/leg-true-course-deg")); @@ -991,7 +980,7 @@ void GPSTests::testOffcourseSequencing() pos3 = SGGeodesy::direct(pos3, 90, 1.8 * SG_NM_TO_METER); fp->setCurrentIndex(6); - setPositionAndStabilise(gps, pos3); + FGTestApi::setPositionAndStabilise(pos3); // start facing the right way pilot->setCourseTrue(gpsNode->getDoubleValue("wp/leg-true-course-deg")); pilot->flyHeading(gpsNode->getDoubleValue("wp/leg-true-course-deg")); @@ -1040,7 +1029,7 @@ void GPSTests::testOffsetFlight() SGGeod pos = fp->pointAlongRoute(2, -10.0); pos = SGGeodesy::direct(pos, 180, 2.0 * SG_NM_TO_METER); - setPositionAndStabilise(gps, pos); + FGTestApi::setPositionAndStabilise(pos); auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot); pilot->resetAtPosition(pos); @@ -1079,7 +1068,7 @@ void GPSTests::testOffsetFlight() fp->setCurrentIndex(3); pos = fp->pointAlongRoute(3, -18.0); pos = SGGeodesy::direct(pos, 180, 2.0 * SG_NM_TO_METER); - setPositionAndStabilise(gps, pos); + FGTestApi::setPositionAndStabilise(pos); pilot->resetAtPosition(pos); // start facing the right way pilot->setCourseTrue(gpsNode->getDoubleValue("wp/leg-true-course-deg")); @@ -1132,7 +1121,7 @@ void GPSTests::testLegIntercept() SGGeod pos = fp->pointAlongRoute(1, 5.0); pos = SGGeodesy::direct(pos, 0, 12.0 * SG_NM_TO_METER); - setPositionAndStabilise(gps, pos); + FGTestApi::setPositionAndStabilise(pos); auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot); pilot->resetAtPosition(pos); pilot->setCourseTrue(180.0); // start facing an annoying direction @@ -1151,7 +1140,7 @@ void GPSTests::testLegIntercept() pos = SGGeodesy::direct(pos, 240, 10.0 * SG_NM_TO_METER); // cycle the GPS mode to re-do the intercept logic - setPositionAndStabilise(gps, pos); + FGTestApi::setPositionAndStabilise(pos); gpsNode->setStringValue("command", "obs"); gpsNode->setStringValue("command", "leg"); @@ -1170,7 +1159,7 @@ void GPSTests::testLegIntercept() pos = SGGeodesy::direct(pos, 180, 5.0 * SG_NM_TO_METER); // cycle the GPS mode to re-do the intercept logic - setPositionAndStabilise(gps, pos); + FGTestApi::setPositionAndStabilise(pos); gpsNode->setStringValue("command", "obs"); gpsNode->setStringValue("command", "leg"); @@ -1189,7 +1178,7 @@ void GPSTests::testLegIntercept() pos = SGGeodesy::direct(pos, 160, 18.0 * SG_NM_TO_METER); // cycle the GPS mode to re-do the intercept logic - setPositionAndStabilise(gps, pos); + FGTestApi::setPositionAndStabilise(pos); gpsNode->setStringValue("command", "obs"); gpsNode->setStringValue("command", "leg"); diff --git a/test_suite/unit_tests/Instrumentation/test_hold_controller.cxx b/test_suite/unit_tests/Instrumentation/test_hold_controller.cxx index 736df7cd3..9e723ea26 100644 --- a/test_suite/unit_tests/Instrumentation/test_hold_controller.cxx +++ b/test_suite/unit_tests/Instrumentation/test_hold_controller.cxx @@ -124,14 +124,14 @@ void HoldControllerTests::setupRouteManager() void HoldControllerTests::testHoldEntryDirect() { - FGTestApi::setUp::logPositionToKML("hold_direct_entry"); + // FGTestApi::setUp::logPositionToKML("hold_direct_entry"); auto rm = globals->get_subsystem<FGRouteMgr>(); auto fp = new FlightPlan; rm->setFlightPlan(fp); FGTestApi::setUp::populateFPWithoutNasal(fp, "EBBR", "07L", "EGGD", "27", "NIK COA DVR TAWNY WOD"); - FGTestApi::writeFlightPlanToKML(fp); + // FGTestApi::writeFlightPlanToKML(fp); auto testDelegate = new TestFPDelegate; testDelegate->thePlan = fp; @@ -238,14 +238,14 @@ void HoldControllerTests::testHoldEntryDirect() void HoldControllerTests::testHoldEntryTeardrop() { - FGTestApi::setUp::logPositionToKML("hold_teardrop_entry"); + // FGTestApi::setUp::logPositionToKML("hold_teardrop_entry"); auto rm = globals->get_subsystem<FGRouteMgr>(); auto fp = new FlightPlan; rm->setFlightPlan(fp); FGTestApi::setUp::populateFPWithoutNasal(fp, "EBBR", "07L", "EGGD", "27", "NIK COA DVR TAWNY WOD"); - FGTestApi::writeFlightPlanToKML(fp); + // FGTestApi::writeFlightPlanToKML(fp); auto testDelegate = new TestFPDelegate; testDelegate->thePlan = fp; @@ -352,14 +352,14 @@ void HoldControllerTests::testHoldEntryTeardrop() void HoldControllerTests::testHoldEntryParallel() { - FGTestApi::setUp::logPositionToKML("hold_parallel_entry"); + //FGTestApi::setUp::logPositionToKML("hold_parallel_entry"); auto rm = globals->get_subsystem<FGRouteMgr>(); auto fp = new FlightPlan; rm->setFlightPlan(fp); FGTestApi::setUp::populateFPWithoutNasal(fp, "EBBR", "07L", "EGGD", "27", "NIK COA DVR TAWNY WOD"); - FGTestApi::writeFlightPlanToKML(fp); + // FGTestApi::writeFlightPlanToKML(fp); auto testDelegate = new TestFPDelegate; testDelegate->thePlan = fp; @@ -456,14 +456,14 @@ void HoldControllerTests::testHoldEntryParallel() void HoldControllerTests::testLeftHoldEntryDirect() { - FGTestApi::setUp::logPositionToKML("hold_left_direct_entry"); + // FGTestApi::setUp::logPositionToKML("hold_left_direct_entry"); auto rm = globals->get_subsystem<FGRouteMgr>(); auto fp = new FlightPlan; rm->setFlightPlan(fp); FGTestApi::setUp::populateFPWithoutNasal(fp, "EBBR", "07L", "EGGD", "27", "NIK COA DVR TAWNY WOD"); - FGTestApi::writeFlightPlanToKML(fp); + // FGTestApi::writeFlightPlanToKML(fp); auto testDelegate = new TestFPDelegate; testDelegate->thePlan = fp; @@ -571,14 +571,14 @@ void HoldControllerTests::testLeftHoldEntryDirect() void HoldControllerTests::testLeftHoldEntryTeardrop() { - FGTestApi::setUp::logPositionToKML("hold_left_teardrop_entry"); + //FGTestApi::setUp::logPositionToKML("hold_left_teardrop_entry"); auto rm = globals->get_subsystem<FGRouteMgr>(); auto fp = new FlightPlan; rm->setFlightPlan(fp); FGTestApi::setUp::populateFPWithoutNasal(fp, "EBBR", "07L", "EGGD", "27", "NIK COA DVR TAWNY WOD"); - FGTestApi::writeFlightPlanToKML(fp); + // FGTestApi::writeFlightPlanToKML(fp); auto testDelegate = new TestFPDelegate; testDelegate->thePlan = fp; @@ -683,14 +683,14 @@ void HoldControllerTests::testLeftHoldEntryTeardrop() void HoldControllerTests::testLeftHoldEntryParallel() { - FGTestApi::setUp::logPositionToKML("hold_left_parallel_entry"); + // FGTestApi::setUp::logPositionToKML("hold_left_parallel_entry"); auto rm = globals->get_subsystem<FGRouteMgr>(); auto fp = new FlightPlan; rm->setFlightPlan(fp); FGTestApi::setUp::populateFPWithoutNasal(fp, "EBBR", "07L", "EGGD", "27", "NIK COA DVR TAWNY WOD"); - FGTestApi::writeFlightPlanToKML(fp); + // FGTestApi::writeFlightPlanToKML(fp); auto testDelegate = new TestFPDelegate; testDelegate->thePlan = fp; @@ -789,14 +789,14 @@ void HoldControllerTests::testLeftHoldEntryParallel() void HoldControllerTests::testHoldNotEntered() { - FGTestApi::setUp::logPositionToKML("hold_no_entry"); + // FGTestApi::setUp::logPositionToKML("hold_no_entry"); auto rm = globals->get_subsystem<FGRouteMgr>(); auto fp = new FlightPlan; rm->setFlightPlan(fp); FGTestApi::setUp::populateFPWithoutNasal(fp, "EBBR", "07L", "EGGD", "27", "NIK COA DVR TAWNY WOD"); - FGTestApi::writeFlightPlanToKML(fp); + // FGTestApi::writeFlightPlanToKML(fp); auto testDelegate = new TestFPDelegate; testDelegate->thePlan = fp; @@ -877,14 +877,14 @@ void HoldControllerTests::testHoldNotEntered() void HoldControllerTests::testHoldEntryOffCourse() { - FGTestApi::setUp::logPositionToKML("hold_entry_off-course"); + // FGTestApi::setUp::logPositionToKML("hold_entry_off-course"); auto rm = globals->get_subsystem<FGRouteMgr>(); auto fp = new FlightPlan; rm->setFlightPlan(fp); FGTestApi::setUp::populateFPWithoutNasal(fp, "EBBR", "07L", "EGGD", "27", "NIK COA DVR TAWNY WOD"); - FGTestApi::writeFlightPlanToKML(fp); + // FGTestApi::writeFlightPlanToKML(fp); auto testDelegate = new TestFPDelegate; testDelegate->thePlan = fp; diff --git a/test_suite/unit_tests/Navaids/test_routeManager.cxx b/test_suite/unit_tests/Navaids/test_routeManager.cxx index cc1110bc5..8b95549ea 100644 --- a/test_suite/unit_tests/Navaids/test_routeManager.cxx +++ b/test_suite/unit_tests/Navaids/test_routeManager.cxx @@ -198,7 +198,6 @@ void RouteManagerTests::testDefaultSID() auto rm = globals->get_subsystem<FGRouteMgr>(); rm->setFlightPlan(fp1); - auto gpsNode = globals->get_props()->getNode("instrumentation/gps", true); auto rmNode = globals->get_props()->getNode("autopilot/route-manager", true); rmNode->setStringValue("departure/sid", "DEFAULT"); @@ -285,3 +284,130 @@ void RouteManagerTests::testDefaultApproach() } +void RouteManagerTests::testHoldFromNasal() +{ + // FGTestApi::setUp::logPositionToKML("rm_hold_from_nasal"); + + // test that Nasal can set a hold-count (implicitly converting a leg + // to a Hold waypt), configure the hold radial, and exit the hold + + FlightPlanRef fp1 = makeTestFP("NZCH", "02", "NZAA", "05L", + "ALADA NS WB WN MAMOD KAPTI OH"); + fp1->setIdent("testplan"); + fp1->setCruiseFlightLevel(360); + + auto rm = globals->get_subsystem<FGRouteMgr>(); + rm->setFlightPlan(fp1); + // FGTestApi::writeFlightPlanToKML(fp1); + + auto gpsNode = globals->get_props()->getNode("instrumentation/gps", true); + CPPUNIT_ASSERT(!strcmp("obs", gpsNode->getStringValue("mode"))); + + rm->activate(); + + CPPUNIT_ASSERT(fp1->isActive()); + CPPUNIT_ASSERT(!strcmp("leg", gpsNode->getStringValue("mode"))); + + SGGeod posEnrouteToWB = fp1->pointAlongRoute(3, -10.0); + FGTestApi::setPositionAndStabilise(posEnrouteToWB); + + // sequence everything to the correct wp + fp1->setCurrentIndex(3); + + // setup some hold data from Nasal. To make it more challenging, + // do it once the wp is already active + bool ok = FGTestApi::executeNasal(R"( + var fp = flightplan(); # retrieve the global flightplan + var leg = fp.getWP(3); + leg.hold_count = 4; + leg.hold_heading_radial_deg = 310; + )"); + CPPUNIT_ASSERT(ok); + + // check the value updated in the leg + CPPUNIT_ASSERT_EQUAL(4, fp1->legAtIndex(3)->holdCount()); + + // check we converted to a hold + auto wp = fp1->legAtIndex(3)->waypoint(); + auto holdWpt = static_cast<flightgear::Hold*>(wp); + + CPPUNIT_ASSERT_EQUAL(wp->type(), std::string{"hold"}); + CPPUNIT_ASSERT_DOUBLES_EQUAL(310.0, holdWpt->headingRadialDeg(), 0.5); + + // establish the test pilot at this position too + auto pilot = SGSharedPtr<FGTestApi::TestPilot>(new FGTestApi::TestPilot); + pilot->resetAtPosition(posEnrouteToWB); + pilot->setSpeedKts(250); + pilot->flyGPSCourse(m_gps); + pilot->setCourseTrue(gpsNode->getDoubleValue("wp/leg-true-course-deg")); + + // run for a bit to stabilize everything + FGTestApi::runForTime(5.0); + + // check we upgraded to a hold controller internally, and are flying to it :) + auto statusNode = gpsNode->getNode("rnav-controller-status"); + CPPUNIT_ASSERT_EQUAL(std::string{"leg-to-hold"}, std::string{statusNode->getStringValue()}); + + // check we're on the leg + + auto wbPos = fp1->legAtIndex(3)->waypoint()->position(); + auto nsPos = fp1->legAtIndex(2)->waypoint()->position(); + const double crsToWB = SGGeodesy::courseDeg(globals->get_aircraft_position(), wbPos); + const double crsNSWB = SGGeodesy::courseDeg(nsPos,wbPos); + + CPPUNIT_ASSERT_DOUBLES_EQUAL(crsToWB, gpsNode->getDoubleValue("wp/wp[1]/bearing-true-deg"), 0.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(crsNSWB, gpsNode->getDoubleValue("wp/leg-true-course-deg"), 0.5); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, gpsNode->getDoubleValue("wp/wp[1]/course-error-nm"), 0.05); + CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, gpsNode->getDoubleValue("wp/wp[1]/course-deviation-deg"), 0.5); + + // fly into the hold, should be teardrop entry + ok = FGTestApi::runForTimeWithCheck(600.0, [statusNode] () { + std::string s = statusNode->getStringValue(); + if (s == "entry-teardrop") return true; + return false; + }); + + CPPUNIT_ASSERT(ok); + + ok = FGTestApi::runForTimeWithCheck(600.0, [statusNode] () { + std::string s = statusNode->getStringValue(); + if (s == "hold-inbound") return true; + return false; + }); + + ok = FGTestApi::runForTimeWithCheck(600.0, [statusNode] () { + std::string s = statusNode->getStringValue(); + if (s == "hold-outbound") return true; + return false; + }); + CPPUNIT_ASSERT(ok); + + // half way through the outbound turn + FGTestApi::runForTime(30.0); + + ok = FGTestApi::executeNasal(R"( + setprop("/instrumentation/gps/command", "exit-hold"); + )"); + CPPUNIT_ASSERT(ok); + + // no change yet + CPPUNIT_ASSERT_EQUAL(std::string{"hold-outbound"}, std::string{statusNode->getStringValue()}); + + // then we fly inbound + ok = FGTestApi::runForTimeWithCheck(600.0, [statusNode] () { + std::string s = statusNode->getStringValue(); + if (s == "hold-inbound") return true; + return false; + }); + CPPUNIT_ASSERT(ok); + + // and then we exit + ok = FGTestApi::runForTimeWithCheck(600.0, [fp1] () { + if (fp1->currentIndex() == 4) return true; + return false; + }); + CPPUNIT_ASSERT(ok); + + // get back on course + FGTestApi::runForTime(60.0); +} diff --git a/test_suite/unit_tests/Navaids/test_routeManager.hxx b/test_suite/unit_tests/Navaids/test_routeManager.hxx index 9e00ed948..c0b8d1c83 100644 --- a/test_suite/unit_tests/Navaids/test_routeManager.hxx +++ b/test_suite/unit_tests/Navaids/test_routeManager.hxx @@ -37,7 +37,7 @@ class RouteManagerTests : public CppUnit::TestFixture CPPUNIT_TEST(testDefaultSID); CPPUNIT_TEST(testDefaultApproach); CPPUNIT_TEST(testDirectToLegOnFlightplanAndResume); - + CPPUNIT_TEST(testHoldFromNasal); CPPUNIT_TEST_SUITE_END(); // void setPositionAndStabilise(FGNavRadio* r, const SGGeod& g); @@ -56,7 +56,7 @@ public: void testDefaultSID(); void testDefaultApproach(); void testDirectToLegOnFlightplanAndResume(); - + void testHoldFromNasal(); private: GPS* m_gps = nullptr; };