FlightGear next
ATCController.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: ATCController.cxx
3 * SPDX-FileComment: Extracted from trafficrecord.cxx - Implementation of AIModels ATC code.
4 * SPDX-FileCopyrightText: Copyright (C) 2006 Durk Talsma
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include <config.h>
9
10#include <algorithm>
11#include <cstdio>
12#include <random>
13
14#include <osg/Geode>
15#include <osg/Geometry>
16#include <osg/MatrixTransform>
17#include <osg/Shape>
18
19#include <simgear/scene/material/EffectGeode.hxx>
20#include <simgear/scene/material/mat.hxx>
21#include <simgear/scene/material/matlib.hxx>
22#include <simgear/scene/util/OsgMath.hxx>
23#include <simgear/timing/sg_time.hxx>
24
25#include <Scenery/scenery.hxx>
26
27#include "atc_mgr.hxx"
28#include "trafficcontrol.hxx"
32#include <Airports/airport.hxx>
33#include <Airports/dynamics.hxx>
35#include <Radio/radio.hxx>
37#include <signal.h>
38
39#include <ATC/ATCController.hxx>
40#include <ATC/atc_mgr.hxx>
42
43using std::sort;
44using std::string;
45
46/***************************************************************************
47 * FGATCController
48 *
49 **************************************************************************/
50
52{
53 dt_count = 0;
54 available = true;
56 initialized = false;
57 lastTransmissionDirection = ATC_AIR_TO_GROUND;
58 group = NULL;
59}
60
62{
63 if (initialized) {
64 auto mgr = globals->get_subsystem<FGATCManager>();
65 mgr->removeController(this);
66 }
67 _isDestroying = true;
69}
70
72{
73 if (!initialized) {
74 auto mgr = globals->get_subsystem<FGATCManager>();
75 mgr->addController(this);
76 initialized = true;
77 }
78}
79
80void FGATCController::setAirportGroundRadar(SGSharedPtr<AirportGroundRadar> groundRadar)
81{
82 airportGroundRadar = groundRadar;
83}
84
86{
87 return ref->atGate();
88}
89
91{
92 return (ac->getCallSign() == fgGetString("/sim/multiplay/callsign")) ? true : false;
93};
94
95bool FGATCController::checkTransmissionState(int minState, int maxState, TrafficVectorIterator i, time_t now, AtcMsgId msgId,
96 AtcMsgDir msgDir)
97{
98 int state = (*i)->getState();
99 if ((state >= minState) && (state <= maxState) && available) {
100 if ((msgDir == ATC_AIR_TO_GROUND) && isUserAircraft((*i)->getAircraft())) {
101 SG_LOG(SG_ATC, SG_BULK, "Checking state " << state << " for " << (*i)->getAircraft()->getCallSign());
102 SGPropertyNode_ptr trans_num = globals->get_props()->getNode("/sim/atc/transmission-num", true);
103 int n = trans_num->getIntValue();
104 if (n == 0) {
105 trans_num->setIntValue(-1);
106 // PopupCallback(n);
107 SG_LOG(SG_ATC, SG_DEBUG, "Selected transmission message " << n);
108 //auto atc = globals->get_subsystem<FGATCManager>();
109 //FGATCDialogNew::instance()->removeEntry(1);
110 } else {
111 SG_LOG(SG_ATC, SG_BULK, "Sending message for " << (*i)->getAircraft()->getCallSign());
112 transmit(*i, parent, msgId, msgDir, false);
113 return false;
114 }
115 }
116 transmit(*i, parent, msgId, msgDir, true);
117 (*i)->updateState();
118 lastTransmission = now;
119 available = false;
120 return true;
121 }
122 return false;
123}
124
126 AtcMsgDir msgDir, bool audible)
127{
128 string sender, receiver;
129 int stationFreq = 0;
130 int taxiFreq = 0;
131 int towerFreq = 0;
132 string atisInformation;
133 string text;
134 string taxiFreqStr;
135 string towerFreqStr;
136 double heading = 0;
137 string activeRunway;
138 string fltType;
139 string rwyClass;
140 string SID;
141 string transponderCode;
142 FGAIFlightPlan* fp;
143 string fltRules;
144 string instructionText;
145 int ground_to_air = 0;
146
147 //double commFreqD;
148 sender = rec->getCallsign();
149 if (rec->getAircraft()->getTaxiClearanceRequest()) {
150 instructionText = "push-back and taxi";
151 } else {
152 instructionText = "taxi";
153 }
154
155 SG_LOG(SG_ATC, SG_BULK, "transmitting for: " << sender << " at Leg " << rec->getLeg());
156
157 //FIXME move departure and arrival to rec
158 auto depApt = rec->getAircraft()->getTrafficRef()->getDepartureAirport();
159 auto arrApt = rec->getAircraft()->getTrafficRef()->getArrivalAirport();
160
161 if (!depApt) {
162 SG_LOG(SG_ATC, SG_DEV_ALERT, "TrafficRec has empty departure airport, can't transmit");
163 return;
164 }
165 if (!arrApt) {
166 SG_LOG(SG_ATC, SG_DEV_ALERT, "TrafficRec has empty arrival airport, can't transmit");
167 return;
168 }
169
170 stationFreq = getFrequency();
171 taxiFreq = depApt->getDynamics()->getGroundFrequency(2);
172 towerFreq = depApt->getDynamics()->getTowerFrequency(2);
173 receiver = getName();
174 atisInformation = depApt->getDynamics()->getAtisSequence();
175
176 // Swap sender and receiver value in case of a ground to air transmission
177 if (msgDir == ATC_GROUND_TO_AIR) {
178 string tmp = sender;
179 sender = receiver;
180 receiver = tmp;
181 ground_to_air = 1;
182 }
183
184 switch (msgId) {
186 text = sender + ". Ready to Start up.";
187 break;
189 text =
190 receiver + ", This is " + sender + ". Position " +
191 getGateName(rec->getAircraft()) + ". Information " +
192 atisInformation + ". " +
193 rec->getAircraft()->getTrafficRef()->getFlightRules() +
194 " to " +
195 rec->getAircraft()->getTrafficRef()->getArrivalAirport()->getName() + ". Request start-up.";
196 break;
197 // Acknowledge engine startup permission
198 // Assign departure runway
199 // Assign SID, if necessary (TODO)
201 taxiFreqStr = formatATCFrequency3_2(taxiFreq);
202
203 heading = rec->getAircraft()->getTrafficRef()->getCourse();
204 fltType = rec->getAircraft()->getTrafficRef()->getFlightType();
205 rwyClass =
206 rec->getAircraft()->GetFlightPlan()->getRunwayClassFromTrafficType(fltType);
207
208 rec->getAircraft()->getTrafficRef()->getDepartureAirport()->getDynamics()->getActiveRunway(rwyClass, RunwayAction::TAKEOFF, activeRunway,
209 heading);
210 rec->getAircraft()->GetFlightPlan()->setRunway(activeRunway);
211 fp = NULL;
212 rec->getAircraft()->GetFlightPlan()->setSID(fp);
213 if (fp) {
214 SID = fp->getName() + " departure";
215 } else {
216 SID = "fly runway heading ";
217 }
218 //snprintf(buffer, 7, "%3.2f", heading);
219 fltRules = rec->getAircraft()->getTrafficRef()->getFlightRules();
220 transponderCode = genTransponderCode(fltRules);
221 rec->getAircraft()->SetTransponderCode(transponderCode);
222 if (stationFreq!=taxiFreq) {
223 text =
224 receiver + ". Start-up approved. " + atisInformation +
225 " correct, runway " + activeRunway + ", " + SID + ", squawk " +
226 transponderCode + ". " +
227 "For " + instructionText + " clearance call " + taxiFreqStr + ". " +
228 sender + " control.";
229 } else {
230 text =
231 receiver + ". Start-up approved. " + atisInformation +
232 " correct, runway " + activeRunway + ", " + SID + ", squawk " +
233 transponderCode + ". " +
234 sender + " control.";
235 }
236 break;
238 text = receiver + ". Standby.";
239 break;
241 fp = rec->getAircraft()->GetFlightPlan()->getSID();
242 if (fp) {
243 SID =
244 rec->getAircraft()->GetFlightPlan()->getSID()->getName() +
245 " departure";
246 } else {
247 SID = "fly runway heading ";
248 }
249 taxiFreqStr = formatATCFrequency3_2(taxiFreq);
250 activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
251 transponderCode = rec->getAircraft()->GetTransponderCode();
252
253 if (stationFreq!=taxiFreq) {
254 text =
255 receiver + ". Start-up approved. " + atisInformation +
256 " correct, runway " + activeRunway + ", " + SID + ", squawk " +
257 transponderCode + ". " +
258 "For " + instructionText + " clearance call " + taxiFreqStr + ". " +
259 sender + ".";
260 } else {
261 text =
262 receiver + ". Start-up approved. " + atisInformation +
263 " correct, runway " + activeRunway + ", " + SID + ", squawk " +
264 transponderCode + ". " +
265 sender + ".";
266 }
267 break;
269 taxiFreqStr = formatATCFrequency3_2(taxiFreq);
270 text = receiver + ". Switching to " + taxiFreqStr + ". " + sender + ".";
271 break;
273 text = receiver + ". With you. " + sender + ".";
274 break;
276 text = receiver + ". Roger. " + sender + ".";
277 break;
279 if (rec->getAircraft()->getTaxiClearanceRequest()) {
280 text = receiver + ". Request push-back. " + sender + ".";
281 } else {
282 text = receiver + ". Request Taxi clearance. " + sender + ".";
283 }
284 break;
286 if (rec->getAircraft()->getTaxiClearanceRequest()) {
287 text = receiver + ". Push-back approved. " + sender + ".";
288 } else {
289 text = receiver + ". Cleared to Taxi. " + sender + ".";
290 }
291 break;
293 text = receiver + ". Standby. " + sender + ".";
294 break;
296 text = receiver + ". Ready to Taxi. " + sender + ".";
297 break;
299 text = receiver + ". Cleared to taxi. " + sender + ".";
300 break;
302 text = receiver + ". Cleared to taxi. " + sender + ".";
303 break;
305 text = receiver + ". Hold Position. " + sender + ".";
306 break;
308 text = receiver + ". Holding Position. " + sender + ".";
309 break;
310 case MSG_RESUME_TAXI:
311 text = receiver + ". Resume Taxiing. " + sender + ".";
312 break;
314 text = receiver + ". Continuing Taxi. " + sender + ".";
315 break;
317 activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
318 text = receiver + ". Holding short runway " + activeRunway + ". " + sender + ".";
319 break;
321 activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
322 text = receiver + " Roger. Holding short runway " + activeRunway + ". " + sender + ".";
323 break;
325 activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
326 text = receiver + ". Cleared for takeoff runway " + activeRunway + ". " + sender + ".";
327 break;
329 activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
330 text = receiver + " Roger. Cleared for takeoff runway " + activeRunway + ". " + sender + ".";
331 break;
333 towerFreqStr = formatATCFrequency3_2(towerFreq);
334 text = receiver + " Contact Tower at " + towerFreqStr + ". " + sender + ".";
335 break;
337 towerFreqStr = formatATCFrequency3_2(towerFreq);
338 text = receiver + " Roger, switching to tower at " + towerFreqStr + ". " + sender + ".";
339 break;
340 case MSG_ARRIVAL:
341 text = receiver + ". " + sender + " Information delta.";
342 break;
344 activeRunway = rec->getRunway();
345 text = receiver + " expect ILS approach " + activeRunway + ". " + sender;
346 break;
348 activeRunway = rec->getRunway();
349 // TODO: Weather
350 text = receiver + " runway " + activeRunway + " cleared to land. " + sender;
351 break;
353 activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
354 text = receiver + " runway " + activeRunway + " cleared to land. " + sender;
355 break;
356 case MSG_HOLD:
357 text = receiver + " hold as published . " + sender;
358 break;
360 text = receiver + " holding as published . " + sender;
361 break;
362 case MSG_TAXI_PARK:
363 if(!rec->getAircraft()->GetFlightPlan()->getParkingGate()) {
364 SG_LOG(SG_ATC, SG_ALERT, "Flightplan without gate");
365 break;
366 }
367 text = receiver + " taxi to " + rec->getAircraft()->GetFlightPlan()->getParkingGate()->getName() + " . " + sender;
368 break;
370 if(!rec->getAircraft()->GetFlightPlan()->getParkingGate()) {
371 SG_LOG(SG_ATC, SG_ALERT, "Flightplan without gate");
372 break;
373 }
374 text = receiver + " taxi to " + rec->getAircraft()->GetFlightPlan()->getParkingGate()->getName() + " . " + sender;
375 break;
376 default:
377 text = text + sender + ". Transmitting unknown Message. MsgId " + std::to_string(msgId);
378 break;
379 }
380
381 const bool atcAudioEnabled = fgGetBool("/sim/sound/atc/enabled", false);
382 if (audible && atcAudioEnabled) {
383 double onBoardRadioFreq0 =
384 fgGetDouble("/instrumentation/comm[0]/frequencies/selected-mhz");
385 double onBoardRadioFreq1 =
386 fgGetDouble("/instrumentation/comm[1]/frequencies/selected-mhz");
387 int onBoardRadioFreqI0 = (int)floor(onBoardRadioFreq0 * 100 + 0.5);
388 int onBoardRadioFreqI1 = (int)floor(onBoardRadioFreq1 * 100 + 0.5);
389 SG_LOG(SG_ATC, SG_DEBUG, "COM1 : " << onBoardRadioFreq0 << " COM2 : " << onBoardRadioFreq1 << " Sending to " << formatATCFrequency3_2(stationFreq) << " Txt : " << text );
390 if (stationFreq == 0) {
391 SG_LOG(SG_ATC, SG_DEBUG, getName() << " stationFreq not found");
392 }
393
394 // Display ATC message only when one of the radios is tuned
395 // the relevant frequency.
396 // Note that distance attenuation is currently not yet implemented
397
398 if ((stationFreq > 0) &&
399 ((onBoardRadioFreqI0 == stationFreq) ||
400 (onBoardRadioFreqI1 == stationFreq))) {
401 if (rec->allowTransmissions()) {
402 if (fgGetBool("/sim/radio/use-itm-attenuation", false)) {
403 SG_LOG(SG_ATC, SG_DEBUG, "Using ITM radio propagation");
405 SGGeod sender_pos;
406 if (ground_to_air) {
407 sender_pos = parent->parent()->geod();
408 } else {
409 sender_pos = rec->getPos();
410 }
411 double frequency = ((double)stationFreq) / 100;
412 radio->receiveATC(sender_pos, frequency, text, ground_to_air);
413 delete radio;
414 } else {
415 SG_LOG(SG_ATC, SG_BULK, "Transmitting " << text);
416 fgSetString("/sim/messages/atc", text.c_str());
417 }
418 }
419 }
420 } else {
421 //FGATCDialogNew::instance()->addEntry(1, text);
422 }
423}
424
425
426SGSharedPtr<FGTrafficRecord> FGATCController::getRecord(int id) const {
428 if (i == activeTraffic.end()) {
429 // Dead traffic should never reach here
430 SG_LOG(SG_ATC, SG_ALERT,
431 "AI error: Aircraft without traffic record " << getName() << " at " << SG_ORIGIN << " list " << activeTraffic.empty());
432 return nullptr;
433 }
434 return *i;
435}
436
443void FGATCController::handover(SGSharedPtr<FGTrafficRecord> aiObject, int leg) {
444 if (aiObject) {
445 aiObject->clearResolveCircularWait();
446 activeTraffic.push_back(aiObject);
447 if (leg == AILeg::LANDING) {
448 // The first contact
449 SG_LOG(SG_ATC, SG_DEBUG,
450 "Added " << (aiObject)->getCallsign() << "(" << (aiObject)->getId() << ") " << aiObject);
451
452 airportGroundRadar->add(aiObject);
453 }
454 }
455}
456
461
463{
465 if (i == activeTraffic.end()) {
466 // Dead traffic should never reach here
467 SG_LOG(SG_ATC, SG_ALERT,
468 "AI error: Aircraft without traffic record is signing off from " << getName() << " at " << SG_ORIGIN << " list " << activeTraffic.empty());
469 return;
470 }
471 const auto leg = (*i)->getLeg();
472 // if taken off or parked
473 if (((leg > AILeg::TAKEOFF && leg < AILeg::APPROACH) ||
474 (leg >= AILeg::PARKING_TAXI)) &&
475 airportGroundRadar != nullptr) {
476 bool result = airportGroundRadar->remove(*i);
477 if (!result) {
478 SG_LOG(SG_ATC, SG_DEV_WARN, "Couldn't remove from index " << (*i));
479 }
480
481 SG_LOG(SG_ATC, SG_DEBUG, (*i)->getCallsign() << " (" << (*i)->getId() << ") signing off from " << getName() << "(" << getFrequency() << ") and removed from AirportGroundradar Leg " << (*i)->getLeg() << " at " << (*i)->getPos());
482 } else {
483 SG_LOG(SG_ATC, SG_DEBUG, (*i)->getCallsign() << " (" << (*i)->getId() << ") signing off from " << getName() << "(" << getFrequency() << ") Leg " << (*i)->getLeg() << " at " << (*i)->getPos());
484 }
485
486 int oldSize = activeTraffic.size();
487 activeTraffic.erase(i);
488 if ((oldSize - activeTraffic.size()) != 1) {
489 SG_LOG(SG_ATC, SG_WARN, (*i)->getCallsign() << " (" << (*i)->getId() << ") not removed ");
490 } else {
491 SG_LOG(SG_ATC, SG_DEBUG, (*i)->getCallsign() << " (" << (*i)->getId() << ") removed from traffic");
492 }
493}
494
496{
497 // Search activeTraffic for a record matching our id
499
500 if (i == activeTraffic.end()) {
501 SG_LOG(SG_ATC, SG_ALERT,
502 "AI error: checking ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
503 } else {
504 return (*i)->hasInstruction();
505 }
506 return false;
507}
508
509
511{
512 if (activeTraffic.size() != 0) {
513 // Search activeTraffic for a record matching our id
515
516 if (i != activeTraffic.end())
517 return (*i)->getInstruction();
518 }
519
520 SG_LOG(SG_ATC, SG_ALERT, "AI error: requesting ATC instruction for aircraft without traffic record from " << getName());
521 return FGATCInstruction();
522}
523
524
525/*
526* Format integer frequency xxxyy as xxx.yy
527* @param freq - integer value
528* @return the formatted string
529*/
531{
532 char buffer[8]; // does this ever need to be freed?
533 if (freq>99999) {
534 snprintf(buffer, 8, "%3.3f", ((float)freq / 1000.0));
535 } else {
536 snprintf(buffer, 8, "%3.3f", ((float)freq / 100.0));
537 }
538 return string(buffer);
539}
540
541// TODO: Set transponder codes according to real-world routes.
542// The current version just returns a random string of four octal numbers.
543string FGATCController::genTransponderCode(const string& fltRules)
544{
545 if (fltRules == "VFR")
546 return string("1200");
547
548 std::uniform_int_distribution<unsigned> distribution(0, 7);
549
550 unsigned val = (distribution(generator) * 1000 +
551 distribution(generator) * 100 +
552 distribution(generator) * 10 +
553 distribution(generator));
554
555 return std::to_string(val);
556}
557
559{
560 auto it = std::remove_if(activeTraffic.begin(), activeTraffic.end(), [](const FGTrafficRecord* traffic) {
561 if (traffic->isDead()) {
562 SG_LOG(SG_ATC, SG_DEBUG, "Remove dead " << traffic->getCallsign() << "(" << traffic->getId() << ") " << traffic->isDead());
563 }
564 return traffic->isDead();
565 });
566 if (it!=activeTraffic.end()) {
567 if (*it) {
568 bool result = airportGroundRadar->remove(*it);
569 if (!result) {
570 SG_LOG(SG_ATC, SG_DEBUG, "Couldn't remove from index " << (*it));
571 }
572 }
573 }
574 activeTraffic.erase(it, activeTraffic.end());
575}
576
583{
584 if (activeTraffic.empty()) {
585 SG_LOG(SG_ATC, SG_DEBUG, "searchActiveTraffic empty list");
586 return activeTraffic.cend();
587 }
588 return std::find_if(activeTraffic.cbegin(), activeTraffic.cend(),
589 [id](const FGTrafficRecord* rec) { return rec->getId() == id; });
590}
591
593{
594 for (const auto& traffic : activeTraffic) {
595 traffic->clearATCController();
596 }
597}
const double rec
#define i(x)
const std::string & atGate()
const std::string & getCallSign() const
Definition AIBase.hxx:367
const std::string & getName()
std::default_random_engine generator
void clearTrafficControllers()
bool hasInstruction(int id)
TrafficVectorIterator searchActiveTraffic(int id) const
Search activeTraffic vector to find matching id.
bool isUserAircraft(FGAIAircraft *)
virtual void handover(SGSharedPtr< FGTrafficRecord > aiObject, int leg)
We share the traffic record much like real life.
virtual ~FGATCController()
virtual void signOff(int id)
Sign off the aircraft with the id from this controller.
FGAirportDynamics * parent
void setAirportGroundRadar(SGSharedPtr< AirportGroundRadar > groundRadar)
SGSharedPtr< FGTrafficRecord > getRecord(int id) const
FGATCInstruction getInstruction(int id)
SGSharedPtr< AirportGroundRadar > airportGroundRadar
TrafficVector activeTraffic
osg::Group * group
virtual int getFrequency()=0
Returns the frequency to be used.
virtual std::string getName() const =0
void transmit(FGTrafficRecord *rec, FGAirportDynamics *parent, AtcMsgId msgId, AtcMsgDir msgDir, bool audible)
std::string genTransponderCode(const std::string &fltRules)
@ MSG_ACKNOWLEDGE_REPORT_RUNWAY_HOLD_SHORT
@ MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY
@ MSG_ACKNOWLEDGE_SWITCH_TOWER_FREQUENCY
std::string formatATCFrequency3_2(int)
bool checkTransmissionState(int minState, int MaxState, TrafficVectorIterator i, time_t now, AtcMsgId msgId, AtcMsgDir msgDir)
std::string getGateName(FGAIAircraft *aircraft)
void addController(FGATCController *controller)
Adds FGATCController instance to std::vector activeStations.
Definition atc_mgr.cxx:280
void removeController(FGATCController *controller)
Searches for and removes FGATCController instance from std::vector activeStations.
Definition atc_mgr.cxx:287
void receiveATC(SGGeod tx_pos, double freq, std::string text, int transmission_type)
Definition radio.cxx:141
bool isDead() const
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
FGGlobals * globals
Definition globals.cxx:142
@ PARKING_TAXI
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30
bool fgSetString(char const *name, char const *str)
Set a string value for a property.
Definition proptest.cpp:26
std::list< SGSharedPtr< FGTrafficRecord > >::const_iterator TrafficVectorIterator