From 998b7fe2f23755a69a762e0ec9550de2265f710a Mon Sep 17 00:00:00 2001 From: Tim Schubert Date: Thu, 27 Aug 2020 17:59:58 +0200 Subject: [PATCH] Fix ground station allocator and trace mac drops --- doc/leo.rst | 2 +- examples/leo-delay-tracing-example.cc | 107 +++++++++++++++++------ helper/ground-node-helper.cc | 8 +- helper/ground-node-helper.h | 5 +- helper/isl-helper.cc | 3 + model/isl-mock-channel.cc | 2 +- model/leo-lat-long.cc | 10 +-- model/leo-lat-long.h | 2 - model/leo-mock-channel.cc | 9 +- model/leo-polar-position-allocator.cc | 118 ++++++++++++++++++++------ model/leo-polar-position-allocator.h | 21 +++-- model/leo-propagation-loss-model.cc | 2 +- model/mock-channel.cc | 1 + model/mock-net-device.cc | 8 +- test/ground-node-helper-test-suite.cc | 4 +- 15 files changed, 222 insertions(+), 80 deletions(-) diff --git a/doc/leo.rst b/doc/leo.rst index 9f9969b..9157098 100644 --- a/doc/leo.rst +++ b/doc/leo.rst @@ -158,7 +158,7 @@ given as pairs of longitude and latitude. --duration=360.0 \ --numGws=120 \ --source=54.4:77.1 \ - --destination=-10.0:25.8 \ + --destination=40.58:-74.97 \ --islRate=1Gbps \ --constellation="StarlinkGateway" \ --interval=1 \ diff --git a/examples/leo-delay-tracing-example.cc b/examples/leo-delay-tracing-example.cc index d0c9f90..73fb8f8 100644 --- a/examples/leo-delay-tracing-example.cc +++ b/examples/leo-delay-tracing-example.cc @@ -22,6 +22,44 @@ EchoRx (std::string context, Ptr packet) std::cout << context << "," << seqTs.GetSeq () << "," << seqTs.GetTs () << "," << Simulator::Now () - seqTs.GetTs () << std::endl; } +static void +EchoTx (std::string context, Ptr packet) +{ + SeqTsHeader seqTs; + Ptr p = packet->Copy (); + p->RemoveHeader (seqTs); + // seqnr, timestamp, delay + std::cout << context << "," << seqTs.GetSeq () << "," << seqTs.GetTs () << "," << Simulator::Now () - seqTs.GetTs () << std::endl; +} + +static void +MacTxDrop (std::string context, Ptr packet) +{ + Ptr p = packet->Copy (); + std::cout << context << ",MacTxDrop," << p << std::endl; +} + +static void +MacRxDrop (std::string context, Ptr packet) +{ + Ptr p = packet->Copy (); + std::cout << context << ",MacRxDrop," << p << std::endl; +} + +static void +PhyTxDrop (std::string context, Ptr packet) +{ + Ptr p = packet->Copy (); + std::cout << context << ",PhyTxDrop," << p << std::endl; +} + +static void +PhyRxDrop (std::string context, Ptr packet) +{ + Ptr p = packet->Copy (); + std::cout << context << ",PhyRxDrop," << p << std::endl; +} + int main (int argc, char *argv[]) { @@ -32,23 +70,29 @@ int main (int argc, char *argv[]) LeoLatLong destination; std::string islRate; std::string constellation; - uint64_t numGws; + uint32_t latGws = 20; + uint32_t lonGws = 20; double interval; double duration; + bool islEnable = false; + bool traceDrops = false; std::string routingProto = "aodv"; cmd.AddValue("orbitFile", "CSV file with orbit parameters", orbitFile); cmd.AddValue("traceFile", "CSV file to store mobility trace in", traceFile); cmd.AddValue("precision", "ns3::LeoCircularOrbitMobilityModel::Precision"); cmd.AddValue("duration", "Duration of the simulation in seconds", duration); - cmd.AddValue("numGws", "Number of gateways", numGws); cmd.AddValue("source", "Traffic source", source); cmd.AddValue("destination", "Traffic destination", destination); - cmd.AddValue("islRate", "Throughput of the ISL link", islRate); + cmd.AddValue("islRate", "ns3::MockNetDevice::DataRate"); cmd.AddValue("constellation", "LEO constellation link settings name", constellation); cmd.AddValue("interval", "Echo interval", interval); cmd.AddValue("routing", "Routing protocol", routingProto); cmd.AddValue("ttlThresh", "ns3::aodv::RoutingProtocol::TtlThreshold"); cmd.AddValue("routeTimeout", "ns3::aodv::RoutingProtocol::ActiveRouteTimeout"); + cmd.AddValue("islEnable", "Enable inter-satellite links", islEnable); + cmd.AddValue("traceDrops", "Enable tracing of PHY and MAC drops", traceDrops); + cmd.AddValue("latGws", "Latitudal rows of gateways", latGws); + cmd.AddValue("latGws", "Longitudinal rows of gateways", lonGws); cmd.Parse (argc, argv); std::streambuf *coutbuf = std::cout.rdbuf(); @@ -64,34 +108,19 @@ int main (int argc, char *argv[]) NodeContainer satellites = orbit.Install (orbitFile); LeoGndNodeHelper ground; - NodeContainer stations = ground.Install (numGws); + NodeContainer stations = ground.Install (latGws, lonGws); + NodeContainer users = ground.Install (source, destination); stations.Add (users); - Ptr client = users.Get (0); - Ptr server = users.Get (1); - - NetDeviceContainer islNet, utNet; - - IslHelper islCh; - islCh.SetDeviceAttribute ("DataRate", StringValue (islRate)); - islCh.SetChannelAttribute ("PropagationDelay", StringValue ("ns3::ConstantSpeedPropagationDelayModel")); - islCh.SetChannelAttribute ("PropagationLoss", StringValue ("ns3::IslPropagationLossModel")); - islNet = islCh.Install (satellites); - LeoChannelHelper utCh; utCh.SetConstellation (constellation); - utNet = utCh.Install (satellites, stations); + NetDeviceContainer utNet = utCh.Install (satellites, stations); InternetStackHelper stack; if (routingProto == "epidemic") { EpidemicHelper epidemic; - epidemic.Set ("HopCount", UintegerValue (50)); - //epidemic.Set ("QueueLength", UintegerValue (50)); - //epidemic.Set ("QueueEntryExpireTime", TimeValue (Seconds (100))); - //epidemic.Set ("BeaconInterval", TimeValue (Seconds (1))); - stack.SetRoutingHelper (epidemic); } else @@ -104,16 +133,24 @@ int main (int argc, char *argv[]) stack.SetRoutingHelper (aodv); } - stack.Install (satellites); stack.Install (stations); - // Make all networks addressable for legacy protocol Ipv4AddressHelper ipv4; + ipv4.SetBase ("10.1.0.0", "255.255.0.0"); - Ipv4InterfaceContainer islIp = ipv4.Assign (islNet); - ipv4.SetBase ("10.3.0.0", "255.255.0.0"); - Ipv4InterfaceContainer utIp = ipv4.Assign (utNet); + ipv4.Assign (utNet); + + if (islEnable) + { + IslHelper islCh; + NetDeviceContainer islNet = islCh.Install (satellites); + ipv4.SetBase ("10.2.0.0", "255.255.0.0"); + ipv4.Assign (islNet); + } + + Ptr client = users.Get (0); + Ptr server = users.Get (1); // we want to ping terminals UdpServerHelper echoServer (9); @@ -130,9 +167,23 @@ int main (int argc, char *argv[]) Config::Connect ("/NodeList/*/ApplicationList/*/$ns3::UdpServer/Rx", MakeCallback (&EchoRx)); + Config::Connect ("/NodeList/*/ApplicationList/*/$ns3::UdpServer/Tx", + MakeCallback (&EchoTx)); - std::cout << "LOCAL =" << client->GetId () << std::endl; - std::cout << "REMOTE=" << server->GetId () << std::endl; + if (traceDrops) + { + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::MockNetDevice/MacTxDrop", + MakeCallback (&MacTxDrop)); + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::MockNetDevice/PhyTxDrop", + MakeCallback (&PhyTxDrop)); + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::MockNetDevice/MacRxDrop", + MakeCallback (&MacRxDrop)); + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::MockNetDevice/PhyRxDrop", + MakeCallback (&PhyRxDrop)); + } + + std::cerr << "LOCAL =" << client->GetId () << std::endl; + std::cerr << "REMOTE=" << server->GetId () << ",addr=" << Ipv4Address::ConvertFrom (remote) << std::endl; std::cout << "Context,Sequence Number,Timestamp,Delay" << std::endl; diff --git a/helper/ground-node-helper.cc b/helper/ground-node-helper.cc index 1cc0f80..e64d010 100644 --- a/helper/ground-node-helper.cc +++ b/helper/ground-node-helper.cc @@ -6,6 +6,7 @@ #include "ns3/config.h" #include "ns3/waypoint.h" #include "ns3/double.h" +#include "ns3/uinteger.h" #include "ns3/mobility-helper.h" #include "ground-node-helper.h" @@ -58,17 +59,18 @@ LeoGndNodeHelper::Install (const std::string &file) } NodeContainer -LeoGndNodeHelper::Install (uint64_t numNodes) +LeoGndNodeHelper::Install (uint32_t latNodes, uint32_t lonNodes) { NodeContainer nodes; - for (uint64_t i = 0; i < numNodes; i++) + for (uint64_t i = 0; i < lonNodes * latNodes; i++) { nodes.Add (m_gndNodeFactory.Create ()); } MobilityHelper mobility; mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel"); mobility.SetPositionAllocator ("ns3::LeoPolarPositionAllocator", - "Step", DoubleValue (360.0 * 180.0 / numNodes)); + "LatNum", UintegerValue (latNodes), + "LonNum", UintegerValue (lonNodes)); mobility.Install (nodes); diff --git a/helper/ground-node-helper.h b/helper/ground-node-helper.h index cc26e6a..f6bbc8d 100644 --- a/helper/ground-node-helper.h +++ b/helper/ground-node-helper.h @@ -36,10 +36,11 @@ public: /** * - * \param numNodes a number of nodes to uniformly distribute accross earth + * \param latNodes a number of nodes to in latitude direction + * \param lonNodes a number of nodes to in longitude direction * \returns a node container containing nodes using the specified attributes */ - NodeContainer Install (uint64_t numNodes); + NodeContainer Install (uint32_t latNodes, uint32_t lonNodes); /** * diff --git a/helper/isl-helper.cc b/helper/isl-helper.cc index c59b846..0c25de0 100644 --- a/helper/isl-helper.cc +++ b/helper/isl-helper.cc @@ -29,6 +29,7 @@ #include "ns3/packet.h" #include "ns3/names.h" #include "ns3/trace-helper.h" +#include "ns3/string.h" #include "../model/mock-net-device.h" #include "../model/isl-mock-channel.h" @@ -43,6 +44,8 @@ IslHelper::IslHelper () m_queueFactory.SetTypeId ("ns3::DropTailQueue"); m_deviceFactory.SetTypeId ("ns3::MockNetDevice"); m_channelFactory.SetTypeId ("ns3::IslMockChannel"); + m_channelFactory.Set ("PropagationDelay", StringValue ("ns3::ConstantSpeedPropagationDelayModel")); + m_channelFactory.Set ("PropagationLoss", StringValue ("ns3::IslPropagationLossModel")); } void diff --git a/model/isl-mock-channel.cc b/model/isl-mock-channel.cc index 6d1bf32..6a3a13c 100644 --- a/model/isl-mock-channel.cc +++ b/model/isl-mock-channel.cc @@ -85,7 +85,7 @@ IslMockChannel::TransmitStart ( } else { - NS_LOG_LOGIC ("destination address " << destAddr << " unknown on channel"); + NS_LOG_ERROR ("destination address " << destAddr << " unknown on channel"); return false; } } diff --git a/model/leo-lat-long.cc b/model/leo-lat-long.cc index 278744e..6c2eb4a 100644 --- a/model/leo-lat-long.cc +++ b/model/leo-lat-long.cc @@ -6,16 +6,15 @@ namespace ns3 { std::ostream &operator << (std::ostream &os, const LeoLatLong &l) { - os << l.label << "," << l.latitude << "," << l.longitude; + os << l.latitude << ":" << l.longitude; return os; } std::istream &operator >> (std::istream &is, LeoLatLong &l) { - char c1, c2; - is >> l.label >> c1 >> l.latitude >> c2 >> l.longitude; - if (c1 != ',' || - c2 != ',') + char c1; + is >> l.latitude >> c1 >> l.longitude; + if (c1 != ':') { is.setstate (std::ios_base::failbit); } @@ -24,7 +23,6 @@ std::istream &operator >> (std::istream &is, LeoLatLong &l) LeoLatLong::LeoLatLong () : latitude (0), longitude (0) {} LeoLatLong::LeoLatLong (double la, double lo) : latitude (la), longitude (lo) {} -LeoLatLong::LeoLatLong (std::string label, double la, double lo) : latitude (la), longitude (lo) {} LeoLatLong::~LeoLatLong () {} }; diff --git a/model/leo-lat-long.h b/model/leo-lat-long.h index 936dbbc..3684432 100644 --- a/model/leo-lat-long.h +++ b/model/leo-lat-long.h @@ -12,10 +12,8 @@ class LeoLatLong public: LeoLatLong (); LeoLatLong (double latitude, double longitude); - LeoLatLong (std::string label, double latitude, double longitude); virtual ~LeoLatLong(); - std::string label; double latitude; double longitude; }; diff --git a/model/leo-mock-channel.cc b/model/leo-mock-channel.cc index e6011ee..18acb69 100644 --- a/model/leo-mock-channel.cc +++ b/model/leo-mock-channel.cc @@ -79,11 +79,16 @@ LeoMockChannel::TransmitStart (Ptr p, return false; } + // make sure to return false if packet has been delivered to *no* device + bool result = false; for (DeviceIndex::iterator it = dests->begin (); it != dests->end(); it ++) { - Deliver (p, srcDev, it->second, txTime); + if (Deliver (p, srcDev, it->second, txTime)) + { + result = true; + } } - return true; + return result; } int32_t diff --git a/model/leo-polar-position-allocator.cc b/model/leo-polar-position-allocator.cc index 1cbc635..29ea652 100644 --- a/model/leo-polar-position-allocator.cc +++ b/model/leo-polar-position-allocator.cc @@ -3,6 +3,8 @@ #include "math.h" #include "ns3/double.h" +#include "ns3/uinteger.h" +#include "ns3/log.h" #include "leo-polar-position-allocator.h" @@ -10,8 +12,10 @@ namespace ns3 { NS_OBJECT_ENSURE_REGISTERED (LeoPolarPositionAllocator); +NS_LOG_COMPONENT_DEFINE ("LeoPolarPositionAllocator"); + LeoPolarPositionAllocator::LeoPolarPositionAllocator () - : m_latStart (0), m_lonStart (0), m_latEnd (0), m_lonEnd (0), m_step (5), m_lat (-1000.0), m_lon (-1000.0) + : m_latStart (0), m_lonStart (0), m_latStop (0), m_lonStop (0), m_latNum (1), m_lonNum (1), m_lat (0), m_lon (0) {} LeoPolarPositionAllocator::~LeoPolarPositionAllocator () @@ -27,28 +31,37 @@ LeoPolarPositionAllocator::GetTypeId (void) .AddAttribute ("LatStart", "Start at this latitude", DoubleValue (-90), - MakeDoubleAccessor (&LeoPolarPositionAllocator::m_latStart), + MakeDoubleAccessor (&LeoPolarPositionAllocator::SetLatStart, + &LeoPolarPositionAllocator::GetLatStart), MakeDoubleChecker ()) .AddAttribute ("LatStop", "Stop at this longitude", DoubleValue (90), - MakeDoubleAccessor (&LeoPolarPositionAllocator::m_latEnd), + MakeDoubleAccessor (&LeoPolarPositionAllocator::SetLatStop, + &LeoPolarPositionAllocator::GetLatStop), MakeDoubleChecker ()) + .AddAttribute ("LatNum", + "The number nodes along one latitude", + UintegerValue (10), + MakeUintegerAccessor (&LeoPolarPositionAllocator::m_latNum), + MakeUintegerChecker ()) .AddAttribute ("LongStart", "Start at this longitude", DoubleValue (-180), - MakeDoubleAccessor (&LeoPolarPositionAllocator::m_lonStart), + MakeDoubleAccessor (&LeoPolarPositionAllocator::SetLonStart, + &LeoPolarPositionAllocator::GetLonStart), MakeDoubleChecker ()) .AddAttribute ("LongStop", "Stop at this longitude", DoubleValue (180), - MakeDoubleAccessor (&LeoPolarPositionAllocator::m_lonEnd), - MakeDoubleChecker ()) - .AddAttribute ("Step", - "The degrees inbetween neighboring locations", - DoubleValue (5), - MakeDoubleAccessor (&LeoPolarPositionAllocator::m_step), + MakeDoubleAccessor (&LeoPolarPositionAllocator::SetLonStop, + &LeoPolarPositionAllocator::GetLonStop), MakeDoubleChecker ()) + .AddAttribute ("LonNum", + "The number nodes along one longitude", + UintegerValue (10), + MakeUintegerAccessor (&LeoPolarPositionAllocator::m_lonNum), + MakeUintegerChecker ()) ; return tid; } @@ -56,33 +69,88 @@ LeoPolarPositionAllocator::GetTypeId (void) int64_t LeoPolarPositionAllocator::AssignStreams (int64_t stream) { + NS_LOG_FUNCTION (this << stream); + return -1; } Vector LeoPolarPositionAllocator::GetNext () const { - m_lat = std::max (m_latStart, m_lat); - m_lon = std::max (m_lonStart, m_lon); + NS_LOG_FUNCTION (this); - double lat = m_lat * (M_PI / 90); - double lon = m_lon * (M_PI / 180); - Vector3D next = Vector3D (LEO_GND_RAD_EARTH * sin (lat) * cos (lon), - LEO_GND_RAD_EARTH * sin (lat) * sin (lon), - LEO_GND_RAD_EARTH * cos (lat)); + double lat = m_lat * (M_PI / m_latNum) - (M_PI / 2); + double lon = m_lon * (2 * M_PI / m_lonNum) - M_PI; + Vector3D next = Vector3D (LEO_GND_RAD_EARTH * cos (lat) * cos (lon), + LEO_GND_RAD_EARTH * cos (lat) * sin (lon), + LEO_GND_RAD_EARTH * sin (lat)); - m_lat = m_lat + m_step; - if (m_lat > m_latEnd) + m_lat ++; + if (m_lat > m_latNum) { - m_lat = m_latStart; - m_lon += m_step; - if (m_lon > m_lonEnd) - { - m_lon = m_lonStart; - } + m_lat = 0; + m_lon = (m_lon+1) % m_lonNum; } + NS_LOG_INFO ("Ground station at " << lat << ":" << lon << " -> " << next); + return next; } +double +LeoPolarPositionAllocator::GetLatStart () const +{ + NS_LOG_FUNCTION (this); + return m_latStart * (180.0 / M_PI); +} + +double +LeoPolarPositionAllocator::GetLonStart () const +{ + NS_LOG_FUNCTION (this); + return m_lonStart * (180.0 / M_PI); +} + +double +LeoPolarPositionAllocator::GetLatStop () const +{ + NS_LOG_FUNCTION (this); + return m_latStop * (180.0 / M_PI); +} + +double +LeoPolarPositionAllocator::GetLonStop () const +{ + NS_LOG_FUNCTION (this); + return m_lonStop * (180.0 / M_PI); +} + +void +LeoPolarPositionAllocator::SetLatStart (double lat) +{ + NS_LOG_FUNCTION (this << lat); + m_latStart = (lat / 180.0) * M_PI; +} + +void +LeoPolarPositionAllocator::SetLonStart (double lon) +{ + NS_LOG_FUNCTION (this << lon); + m_lonStart = (lon / 180.0) * M_PI; +} + +void +LeoPolarPositionAllocator::SetLatStop (double lat) +{ + NS_LOG_FUNCTION (this << lat); + m_latStop = (lat / 180.0) * M_PI; +} + +void +LeoPolarPositionAllocator::SetLonStop (double lon) +{ + NS_LOG_FUNCTION (this << lon); + m_lonStop = (lon / 180.0) * M_PI; +} + }; diff --git a/model/leo-polar-position-allocator.h b/model/leo-polar-position-allocator.h index d4d64a4..7c55c29 100644 --- a/model/leo-polar-position-allocator.h +++ b/model/leo-polar-position-allocator.h @@ -27,17 +27,28 @@ public: virtual Vector GetNext (void) const; virtual int64_t AssignStreams (int64_t stream); + double GetLatStart () const; + double GetLonStart () const; + double GetLatStop () const; + double GetLonStop () const; + private: double m_latStart; double m_lonStart; - double m_latEnd; - double m_lonEnd; + double m_latStop; + double m_lonStop; - double m_step; + uint32_t m_latNum; + uint32_t m_lonNum; - mutable double m_lat; - mutable double m_lon; + mutable uint32_t m_lat; + mutable uint32_t m_lon; + + void SetLatStart (double lat); + void SetLonStart (double lon); + void SetLatStop (double lat); + void SetLonStop (double lon); }; }; diff --git a/model/leo-propagation-loss-model.cc b/model/leo-propagation-loss-model.cc index fe0461e..490fb80 100644 --- a/model/leo-propagation-loss-model.cc +++ b/model/leo-propagation-loss-model.cc @@ -23,7 +23,7 @@ LeoPropagationLossModel::GetTypeId (void) .AddConstructor () .AddAttribute ("MaxDistance", "Cut-off distance for signal propagation", - DoubleValue (2000.0), + DoubleValue (3000.0), MakeDoubleAccessor (&LeoPropagationLossModel::SetCutoffDistance, &LeoPropagationLossModel::GetCutoffDistance), MakeDoubleChecker ()) diff --git a/model/mock-channel.cc b/model/mock-channel.cc index 0a03f08..2e177ca 100644 --- a/model/mock-channel.cc +++ b/model/mock-channel.cc @@ -170,6 +170,7 @@ MockChannel::Deliver ( rxPower = pLoss->CalcRxPower (txPower, srcMob, dstMob); if (rxPower <= -1000.0) { + NS_LOG_WARN (this << "unable to reach destination " << dst->GetNode ()->GetId () << " from " << src->GetNode ()->GetId ()); return false; } } diff --git a/model/mock-net-device.cc b/model/mock-net-device.cc index 3767093..900959a 100644 --- a/model/mock-net-device.cc +++ b/model/mock-net-device.cc @@ -102,14 +102,12 @@ MockNetDevice::GetTypeId (void) "This is a non-promiscuous trace,", MakeTraceSourceAccessor (&MockNetDevice::m_macRxTrace), "ns3::Packet::TracedCallback") -#if 0 // Not currently implemented for this device .AddTraceSource ("MacRxDrop", "Trace source indicating a packet was dropped " "before being forwarded up the stack", MakeTraceSourceAccessor (&MockNetDevice::m_macRxDropTrace), "ns3::Packet::TracedCallback") -#endif // // Trace sources at the "bottom" of the net device, where packets transition // to/from the channel. @@ -413,6 +411,7 @@ MockNetDevice::Receive (Ptr packet, if (senderDevice == this) { + m_macRxDropTrace (packet); return; } @@ -684,10 +683,15 @@ MockNetDevice::Send (Ptr packet, m_snifferTrace (packet); TransmitStart (packet, dest); } + else + { + NS_LOG_LOGIC (this << "channel not ready, postponing transmit start: " << m_queue->GetCurrentSize () << "/" << m_queue->GetMaxSize ()); + } return true; } // Enqueue may fail (overflow) + NS_LOG_WARN ("queue overflowed: " << m_queue->GetCurrentSize () << "/" << m_queue->GetMaxSize ()); m_macTxDropTrace (packet); return false; diff --git a/test/ground-node-helper-test-suite.cc b/test/ground-node-helper-test-suite.cc index 8bfd570..43c508b 100644 --- a/test/ground-node-helper-test-suite.cc +++ b/test/ground-node-helper-test-suite.cc @@ -68,8 +68,8 @@ void SomeGndNodeHelperTestCase::DoRun (void) { LeoGndNodeHelper gndHelper; - NodeContainer nodes = gndHelper.Install (LeoLatLong ("station1", 50.1, 10.0), - LeoLatLong ("station2", -70.1, -21.0)); + NodeContainer nodes = gndHelper.Install (LeoLatLong (50.1, 10.0), + LeoLatLong (-70.1, -21.0)); NS_ASSERT_MSG (nodes.GetN () == 2, "No ground stations");