diff --git a/src/ATC/CommStation.cxx b/src/ATC/CommStation.cxx index 867bc4569..85fc0c267 100644 --- a/src/ATC/CommStation.cxx +++ b/src/ATC/CommStation.cxx @@ -1,3 +1,9 @@ +/* + * SPDX-FileName: CommStation.cxx + * SPDX-FileComment: class describing a single comm station in the Nav DB + * SPDX-License-Identifier: GPL-2.0-or-later + */ + #include "config.h" #include "CommStation.hxx" diff --git a/src/ATC/CommStation.hxx b/src/ATC/CommStation.hxx index d1a6b26f3..9d35a24b4 100644 --- a/src/ATC/CommStation.hxx +++ b/src/ATC/CommStation.hxx @@ -1,5 +1,10 @@ -#ifndef FG_ATC_COMM_STATION_HXX -#define FG_ATC_COMM_STATION_HXX +/* + * SPDX-FileName: CommStation.hxx + * SPDX-FileComment: class describing a single comm station in the Nav DB + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once #include #include @@ -24,13 +29,16 @@ public: double freqMHz() const; static CommStationRef findByFreq(int freqKhz, const SGGeod& pos, FGPositioned::Filter* filt = NULL); + + static bool isType(FGPositioned::Type ty) + { + return (ty >= FGPositioned::FREQ_GROUND) && (ty <= FGPositioned::FREQ_UNICOM); + } + private: int mRangeNM; int mFreqKhz; PositionedID mAirport; }; -} // of namespace flightgear - -#endif // of FG_ATC_COMM_STATION_HXX - +} // namespace flightgear diff --git a/src/Navaids/positioned.cxx b/src/Navaids/positioned.cxx index 86ccbd648..00c5c8904 100644 --- a/src/Navaids/positioned.cxx +++ b/src/Navaids/positioned.cxx @@ -439,6 +439,14 @@ FGPositioned::TypeFilter::TypeFilter(std::initializer_list types) } } +FGPositioned::TypeFilter::TypeFilter(Type aMinType, Type aMaxType) +{ + for (int t = aMinType; t <= aMaxType; t++) { + addType(static_cast(t)); + } +} + + void FGPositioned::TypeFilter::addType(Type aTy) { if (aTy == INVALID) { diff --git a/src/Navaids/positioned.hxx b/src/Navaids/positioned.hxx index fe03533c6..656da33d9 100644 --- a/src/Navaids/positioned.hxx +++ b/src/Navaids/positioned.hxx @@ -44,59 +44,63 @@ class FGPositioned : public SGReferenced public: static const PositionedID TRANSIENT_ID; - typedef enum { - INVALID = 0, - AIRPORT, - HELIPORT, - SEAPORT, - RUNWAY, - HELIPAD, - TAXIWAY, - PAVEMENT, - WAYPOINT, - FIX, - NDB, - VOR, - ILS, - LOC, - GS, - OM, - MM, - IM, -/// important that DME & TACAN are adjacent to keep the TacanFilter -/// efficient - DMEs are proxies for TACAN/VORTAC stations - DME, - TACAN, - MOBILE_TACAN, - OBSTACLE, -/// an actual airport tower - not a radio comms facility! -/// some airports have multiple towers, eg EHAM, although our data source -/// doesn't necessarily include them - TOWER, - FREQ_GROUND, - FREQ_TOWER, - FREQ_ATIS, - FREQ_AWOS, - FREQ_APP_DEP, - FREQ_ENROUTE, - FREQ_CLEARANCE, - FREQ_UNICOM, -// groundnet items - PARKING, ///< parking position - might be a gate, or stand - TAXI_NODE, -// POI items - COUNTRY, - CITY, - TOWN, - VILLAGE, - - LAST_TYPE - } Type; + typedef enum { + INVALID = 0, + AIRPORT, + HELIPORT, + SEAPORT, + RUNWAY, + HELIPAD, + TAXIWAY, + PAVEMENT, + WAYPOINT, + FIX, + NDB, + VOR, + ILS, + LOC, + GS, + OM, + MM, + IM, + /// important that DME & TACAN are adjacent to keep the TacanFilter + /// efficient - DMEs are proxies for TACAN/VORTAC stations + DME, + TACAN, + MOBILE_TACAN, + OBSTACLE, + /// an actual airport tower - not a radio comms facility! + /// some airports have multiple towers, eg EHAM, although our data source + /// doesn't necessarily include them + TOWER, + //comm stations : if extending this, be sure to update the isType check in + // CommStation.hxx + FREQ_GROUND, + FREQ_TOWER, + FREQ_ATIS, + FREQ_AWOS, + FREQ_APP_DEP, + FREQ_ENROUTE, + FREQ_CLEARANCE, + FREQ_UNICOM, + // groundnet items + PARKING, ///< parking position - might be a gate, or stand + TAXI_NODE, + // POI items + COUNTRY, + CITY, + TOWN, + VILLAGE, - virtual ~FGPositioned(); - - Type type() const - { return mType; } + LAST_TYPE + } Type; + + virtual ~FGPositioned(); + + Type type() const + { + return mType; + } // True for the following types: AIRPORT, HELIPORT, SEAPORT. // False for other types, as well as if pos == nullptr. @@ -177,7 +181,15 @@ public: TypeFilter(Type aTy = INVALID); TypeFilter(std::initializer_list types); - + + /** + * @brief Construct a new Type Filter based on a sequential range of types + * + * @param aMinType + * @param aMaxType + */ + TypeFilter(Type aMinType, Type aMaxType); + bool pass(FGPositioned* aPos) const override; Type minType() const override diff --git a/src/Scripting/NasalPositioned.cxx b/src/Scripting/NasalPositioned.cxx index bd721b8c0..1d1247545 100644 --- a/src/Scripting/NasalPositioned.cxx +++ b/src/Scripting/NasalPositioned.cxx @@ -69,6 +69,9 @@ static naGhostType TaxiwayGhostType = { positionedGhostDestroy, "taxiway", runwa static const char* fixGhostGetMember(naContext c, void* g, naRef field, naRef* out); static naGhostType FixGhostType = { positionedGhostDestroy, "fix", fixGhostGetMember, nullptr }; +static const char* commGhostGetMember(naContext c, void* g, naRef field, naRef* out); +static naGhostType CommGhostType = {positionedGhostDestroy, "comm", commGhostGetMember, nullptr}; + static void hashset(naContext c, naRef hash, const char* key, naRef val) { naRef s = naNewString(c); @@ -88,8 +91,8 @@ FGPositioned* positionedGhost(naRef r) if ((naGhost_type(r) == &AirportGhostType) || (naGhost_type(r) == &NavaidGhostType) || (naGhost_type(r) == &RunwayGhostType) || - (naGhost_type(r) == &FixGhostType)) - { + (naGhost_type(r) == &FixGhostType) || + (naGhost_type(r) == &CommGhostType)) { return (FGPositioned*) naGhost_ptr(r); } @@ -131,6 +134,12 @@ static FGFix* fixGhost(naRef r) return 0; } +static flightgear::CommStation* commGhost(naRef r) +{ + if (naGhost_type(r) == &CommGhostType) + return (flightgear::CommStation*)naGhost_ptr(r); + return nullptr; +} static void positionedGhostDestroy(void* g) { @@ -203,6 +212,16 @@ naRef ghostForFix(naContext c, const FGFix* r) return naNewGhost2(c, &FixGhostType, (void*) r); } +naRef ghostForComm(naContext c, const flightgear::CommStation* comm) +{ + if (!c) { + return naNil(); + } + + FGPositioned::get(comm); // take a ref + return naNewGhost2(c, &CommGhostType, (void*)comm); +} + naRef ghostForPositioned(naContext c, FGPositionedRef pos) { if (!pos) { @@ -222,6 +241,16 @@ naRef ghostForPositioned(naContext c, FGPositionedRef pos) return ghostForHelipad(c, fgpositioned_cast(pos)); case FGPositioned::RUNWAY: return ghostForRunway(c, fgpositioned_cast(pos)); + case FGPositioned::FREQ_APP_DEP: + case FGPositioned::FREQ_AWOS: + case FGPositioned::FREQ_GROUND: + case FGPositioned::FREQ_TOWER: + case FGPositioned::FREQ_ATIS: + case FGPositioned::FREQ_CLEARANCE: + case FGPositioned::FREQ_UNICOM: + case FGPositioned::FREQ_ENROUTE: + return ghostForComm(c, fgpositioned_cast(pos)); + default: SG_LOG(SG_NASAL, SG_DEV_ALERT, "Type lacks Nasal ghost mapping:" << pos->typeString()); return naNil(); @@ -387,6 +416,31 @@ static const char* fixGhostGetMember(naContext c, void* g, naRef field, naRef* o return ""; } +static const char* commGhostGetMember(naContext c, void* g, naRef field, naRef* out) +{ + const char* fieldName = naStr_data(field); + auto comm = static_cast(g); + + if (!strcmp(fieldName, "id")) + *out = stringToNasal(c, comm->ident()); + else if (!strcmp(fieldName, "lat")) + *out = naNum(comm->latitude()); + else if (!strcmp(fieldName, "lon")) + *out = naNum(comm->longitude()); + else if (!strcmp(fieldName, "type")) { + *out = stringToNasal(c, comm->nameForType(comm->type())); + } else if (!strcmp(fieldName, "name")) + *out = stringToNasal(c, comm->name()); + else if (!strcmp(fieldName, "frequency")) { + *out = naNum(comm->freqMHz()); + } else { + return 0; + } + + return ""; +} + + static bool hashIsCoord(naRef h) { naRef parents = naHash_cget(h, (char*) "parents"); @@ -1364,6 +1418,34 @@ static naRef f_findNavaidsByFrequency(naContext c, naRef me, int argc, naRef* ar return r; } +static naRef f_findCommByFrequency(naContext c, naRef me, int argc, naRef* args) +{ + int argOffset = 0; + SGGeod pos = globals->get_aircraft_position(); + argOffset += geodFromArgs(args, 0, argc, pos); + + if (!naIsNum(args[argOffset])) { + naRuntimeError(c, "findCommByFrequencyMhz expectes frequency (in Mhz) as arg %d", argOffset); + } + + // initial filter is all comm types + FGPositioned::TypeFilter filter(FGPositioned::FREQ_GROUND, FGPositioned::FREQ_UNICOM); + + double freqMhz = args[argOffset++].num; + if (argOffset < argc) { + // allow specifying an explicitly type by name + filter = FGPositioned::TypeFilter{FGPositioned::typeFromName(naStr_data(args[argOffset]))}; + } + + auto ref = NavDataCache::instance()->findCommByFreq(static_cast(freqMhz * 1000), pos, &filter); + if (!ref) { + return naNil(); + } + + auto comm = fgpositioned_cast(ref); + return ghostForComm(c, comm); +} + static naRef f_findNavaidsByIdent(naContext c, naRef me, int argc, naRef* args) { int argOffset = 0; @@ -1600,33 +1682,36 @@ FGPositionedRef positionedFromArg(naRef ref) } // Table of extension functions. Terminate with zeros. -static struct { const char* name; naCFunction func; } funcs[] = { - { "carttogeod", f_carttogeod }, - { "geodtocart", f_geodtocart }, - { "geodinfo", f_geodinfo }, - { "formatLatLon", f_formatLatLon }, - { "parseStringAsLatLonValue", f_parseStringAsLatLonValue}, - { "get_cart_ground_intersection", f_get_cart_ground_intersection }, - { "aircraftToCart", f_aircraftToCart }, - { "airportinfo", f_airportinfo }, - { "findAirportsWithinRange", f_findAirportsWithinRange }, - { "findAirportsByICAO", f_findAirportsByICAO }, - { "navinfo", f_navinfo }, - { "findNavaidsWithinRange", f_findNavaidsWithinRange }, - { "findNDBByFrequencyKHz", f_findNDBByFrequency }, - { "findNDBsByFrequencyKHz", f_findNDBsByFrequency }, - { "findNavaidByFrequencyMHz", f_findNavaidByFrequency }, - { "findNavaidsByFrequencyMHz", f_findNavaidsByFrequency }, - { "findNavaidsByID", f_findNavaidsByIdent }, - { "findFixesByID", f_findFixesByIdent }, - { "findByIdent", f_findByIdent }, - { "magvar", f_magvar }, - { "courseAndDistance", f_courseAndDistance }, - { "greatCircleMove", f_greatCircleMove }, - { "tileIndex", f_tileIndex }, - { "tilePath", f_tilePath }, - { 0, 0 } -}; +static struct { + const char* name; + naCFunction func; +} funcs[] = { + {"carttogeod", f_carttogeod}, + {"geodtocart", f_geodtocart}, + {"geodinfo", f_geodinfo}, + {"formatLatLon", f_formatLatLon}, + {"parseStringAsLatLonValue", f_parseStringAsLatLonValue}, + {"get_cart_ground_intersection", f_get_cart_ground_intersection}, + {"aircraftToCart", f_aircraftToCart}, + {"airportinfo", f_airportinfo}, + {"findAirportsWithinRange", f_findAirportsWithinRange}, + {"findAirportsByICAO", f_findAirportsByICAO}, + {"navinfo", f_navinfo}, + {"findNavaidsWithinRange", f_findNavaidsWithinRange}, + {"findNDBByFrequencyKHz", f_findNDBByFrequency}, + {"findNDBsByFrequencyKHz", f_findNDBsByFrequency}, + {"findNavaidByFrequencyMHz", f_findNavaidByFrequency}, + {"findNavaidsByFrequencyMHz", f_findNavaidsByFrequency}, + {"findCommByFrequencyMHz", f_findCommByFrequency}, + {"findNavaidsByID", f_findNavaidsByIdent}, + {"findFixesByID", f_findFixesByIdent}, + {"findByIdent", f_findByIdent}, + {"magvar", f_magvar}, + {"courseAndDistance", f_courseAndDistance}, + {"greatCircleMove", f_greatCircleMove}, + {"tileIndex", f_tileIndex}, + {"tilePath", f_tilePath}, + {0, 0}}; naRef initNasalPositioned(naRef globals, naContext c) diff --git a/test_suite/unit_tests/Main/test_commands.cxx b/test_suite/unit_tests/Main/test_commands.cxx index 2ec8e2303..2aa129bf4 100644 --- a/test_suite/unit_tests/Main/test_commands.cxx +++ b/test_suite/unit_tests/Main/test_commands.cxx @@ -29,7 +29,7 @@ void CommandsTests::setUp() void CommandsTests::tearDown() { - delete SGCommandMgr::instance(); + FGTestApi::tearDown::shutdownTestGlobals(); } void CommandsTests::testPropertyAdjustCommand() diff --git a/test_suite/unit_tests/Scripting/testNasalSys.cxx b/test_suite/unit_tests/Scripting/testNasalSys.cxx index ea68a59b5..bf85dd165 100644 --- a/test_suite/unit_tests/Scripting/testNasalSys.cxx +++ b/test_suite/unit_tests/Scripting/testNasalSys.cxx @@ -29,6 +29,8 @@ #include
#include
+#include + #include #include
@@ -59,6 +61,12 @@ void NasalSysTests::tearDown() FGTestApi::tearDown::shutdownTestGlobals(); } +bool NasalSysTests::checkNoNasalErrors() +{ + auto nasalSys = globals->get_subsystem(); + auto errors = nasalSys->getAndClearErrorList(); + return errors.empty(); +} // Test test void NasalSysTests::testStructEquality() @@ -169,6 +177,31 @@ void NasalSysTests::testAirportGhost() } +void NasalSysTests::testFindComm() +{ + FGAirportRef apt = FGAirport::getByIdent("EDDM"); + FGTestApi::setPositionAndStabilise(apt->geod()); + + auto nasalSys = globals->get_subsystem(); + nasalSys->getAndClearErrorList(); + + bool ok = FGTestApi::executeNasal(R"( + var comm = findCommByFrequencyMHz(123.125); + unitTest.assert_equal(comm.id, "ATIS"); + + # explicit filter, should't match + var noComm = findCommByFrequencyMHz(123.125, "tower"); + unitTest.assert_equal(noComm, nil); + + # match with filter + var comm2 = findCommByFrequencyMHz(121.725, "clearance"); + unitTest.assert_equal(comm2.id, "CLNC DEL"); + )"); + + CPPUNIT_ASSERT(ok && checkNoNasalErrors()); +} + + // https://sourceforge.net/p/flightgear/codetickets/2246/ void NasalSysTests::testCompileLarge() diff --git a/test_suite/unit_tests/Scripting/testNasalSys.hxx b/test_suite/unit_tests/Scripting/testNasalSys.hxx index cef809152..b461f6d47 100644 --- a/test_suite/unit_tests/Scripting/testNasalSys.hxx +++ b/test_suite/unit_tests/Scripting/testNasalSys.hxx @@ -40,8 +40,11 @@ class NasalSysTests : public CppUnit::TestFixture CPPUNIT_TEST(testKeywordArgInHash); CPPUNIT_TEST(testNullAccess); CPPUNIT_TEST(testNullishChain); + CPPUNIT_TEST(testFindComm); CPPUNIT_TEST_SUITE_END(); + bool checkNoNasalErrors(); + public: // Set up function for each test. void setUp(); @@ -59,6 +62,7 @@ public: void testKeywordArgInHash(); void testNullAccess(); void testNullishChain(); + void testFindComm(); }; #endif // _FG_NASALSYS_UNIT_TESTS_HXX