From 32b9c6152817326666c5d29e28c334c0559b088e Mon Sep 17 00:00:00 2001 From: ThorstenB Date: Sat, 13 Oct 2012 16:48:56 +0200 Subject: [PATCH] ATIS upgrade Add missing ATIS elements (transition level, expected approach, precipitation, runway surface warnings). Minor phraseology fixes for US/Europe. Adds "/sim/atis/concise-report" option to use abbreviations (CAVOK etc), and omit "obvious" units (depends on airport). Trigger ATIS updates for significant pressure changes. --- src/ATCDCL/atis.cxx | 797 ++++++++++++++++++++++++------------ src/ATCDCL/atis.hxx | 53 ++- src/ATCDCL/atis_lexicon.hxx | 64 ++- src/Main/fg_init.cxx | 2 + 4 files changed, 646 insertions(+), 270 deletions(-) diff --git a/src/ATCDCL/atis.cxx b/src/ATCDCL/atis.cxx index c8c79f639..3bc9e2f1f 100644 --- a/src/ATCDCL/atis.cxx +++ b/src/ATCDCL/atis.cxx @@ -2,6 +2,7 @@ // This is the implementation of the FGATIS class // // Written by David Luff, started October 2001. +// Extended by Thorsten Brehm, October 2012. // // Copyright (C) 2001 David C Luff - david.luff@nottingham.ac.uk // @@ -36,6 +37,7 @@ #include #include #include +#include #include // atoi() #include // sprintf @@ -56,6 +58,7 @@ #include #include +#include #include "ATCutils.hxx" #include "ATISmgr.hxx" @@ -80,6 +83,7 @@ FGATIS::FGATIS(const std::string& name, int num) : cur_time(0), msg_OK(0), _attention(false), + _check_transmission(true), _prev_display(0), _time_before_search_sec(0), _last_frequency(0) @@ -117,15 +121,18 @@ FGATIS::FGATIS(const std::string& name, int num) : // // Load the remap list from the .hxx file: using namespace lex; -# define NIL "" -# define REMAP(from,to) _remap[#from] = to; -# include "atis_remap.hxx" -# undef REMAP -# undef NIL -#ifdef ATIS_TEST - SG_LOG(SG_ATC, SG_ALERT, "ATIS initialized"); -#endif + # define NIL "" + # define REMAP(from,to) _remap[#from] = to; + # include "atis_remap.hxx" + # undef REMAP + # undef NIL + + #ifdef ATIS_TEST + SG_LOG(SG_ATC, SG_ALERT, "ATIS initialized"); + #endif + + _report.psl = 0; } // Hint: @@ -143,19 +150,27 @@ FGATCVoice* FGATIS::GetVoicePointer() return pAtisMgr->GetVoicePointer(ATIS); } -void FGATIS::init() { +void FGATIS::init() +{ // Nothing to see here. Move along. } +void FGATIS::reinit() +{ + _time_before_search_sec = 0; + _check_transmission = true; +} + void FGATIS::attend(SGPropertyNode* node) { - _attention = node->getBoolValue(); + if (node->getBoolValue()) + _attention = true; #ifdef ATMO_TEST int flag = fgGetInt("/sim/logging/atmo"); if (flag) { FGAltimeter().check_model(); - FGAltimeter().dump_stack(); + FGAltimeter().dump_stack(); } #endif } @@ -179,13 +194,11 @@ void FGATIS::update(double dt) { double volume = 0; if ((_electrical->getDoubleValue() > 8) && _serviceable->getBoolValue()) { - _time_before_search_sec -= dt; // radio is switched on and OK if (_operable.valid()) _operable->setBoolValue(true); - // Search the tuned frequencies - search(); + _check_transmission |= search(dt); if (_display) { @@ -202,14 +215,21 @@ void FGATIS::update(double dt) { if (volume > 0.05) { - // Check if we need to update the message - // - basically every hour and if the weather changes significantly at the station - // If !_prev_display, the radio had been detuned for a while and our - // "transmission" variable was lost when we were de-instantiated. - int changed = genTransmission(!_prev_display, _attention); - - // update output property - TreeOut(msg_OK); + bool changed = false; + if (_check_transmission) + { + _check_transmission = false; + // Check if we need to update the message + // - basically every hour and if the weather changes significantly at the station + // If !_prev_display, the radio had been detuned for a while and our + // "transmission" variable was lost when we were de-instantiated. + if (genTransmission(!_prev_display, _attention)) + { + // update output property + treeOut(msg_OK); + changed = true; + } + } if (changed || volume != old_volume) { // audio output enabled @@ -227,14 +247,6 @@ void FGATIS::update(double dt) { FGATC::update(dt); } -string uppercase(const string &s) { - string rslt(s); - for(string::iterator p = rslt.begin(); p != rslt.end(); p++){ - *p = toupper(*p); - } - return rslt; -} - // Replace all occurrences of a given word. // Words in the original string must be separated by hyphens (not spaces). // We check for the word as given, and for the all-caps version thereof. @@ -251,7 +263,7 @@ string replace_word(const string _orig, const string _www, const string _nnn){ where += nnn.length(); } - www = uppercase(www); + www = simgear::strutils::uppercase(www); for ( ; (where = orig.find(www, where)) != string::npos ; ) { orig.replace(where, www.length(), nnn); where += nnn.length(); @@ -278,200 +290,366 @@ const int minute(60); // measured in seconds // ascertaining which airports are in the US, let alone // (b) ascertaining which other places use inches. // -bool Apt_US_CA(const string id) { -// Assume all IDs have length 3 or 4. -// No counterexamples have been seen. - if (id.length() == 4) { - if (id.substr(0,1) == "K") return true; - if (id.substr(0,2) == "CY") return true; - } - for (string::const_iterator ptr = id.begin(); ptr != id.end(); ptr++) { - if (isdigit(*ptr)) return true; - } - return false; +bool Apt_US_CA(const string id) +{ + // Assume all IDs have length 3 or 4. + // No counterexamples have been seen. + if (id.length() == 4) { + if (id.substr(0,1) == "K") return true; + if (id.substr(0,2) == "CY") return true; + } + for (string::const_iterator ptr = id.begin(); ptr != id.end(); ptr++) { + if (isdigit(*ptr)) return true; + } + return false; } -static string BRK = ".\n"; -static string PAUSE = " / "; +// voice spacers +static const string BRK = ".\n"; +static const string PAUSE = " / "; /** Generate the actual broadcast ATIS transmission. * 'regen' triggers a regeneration of the /current/ transmission. -* 'special' generates a new transmission, with a new sequence. +* 'forceUpdate' generates a new transmission, with a new sequence. * Returns 1 if we actually generated something. */ -int FGATIS::genTransmission(const int regen, const bool special) { - using namespace atmodel; - using namespace lex; +bool FGATIS::genTransmission(const int regen, bool forceUpdate) +{ + using namespace lex; - // ATIS updated hourly, AWOS updated more frequently - int interval = _type == ATIS ? ATIS_interval : 2*minute; + // ATIS updated hourly, AWOS updated more frequently + int interval = _type == ATIS ? ATIS_interval : 2*minute; - FGAirport* apt = FGAirport::findByIdent(ident); - int sequence = apt->getDynamics()->updateAtisSequence(interval, special); - if (!regen && sequence > LTRS) { - //xx if (msg_OK) cout << "ATIS: no change: " << sequence << endl; - //xx msg_time = cur_time; - return 0; // no change since last time - } + // check if pressure has changed significantly and we need to update ATIS + double Psl = fgGetDouble("/environment/pressure-sea-level-inhg"); + if (fabs(Psl-_report.psl) >= 0.15) + forceUpdate = true; + + FGAirport* apt = FGAirport::findByIdent(ident); + int sequence = apt->getDynamics()->updateAtisSequence(interval, forceUpdate); + if (!regen && sequence > LTRS) { + //xx if (msg_OK) cout << "ATIS: no change: " << sequence << endl; + //xx msg_time = cur_time; + return false; // no change since last time + } - transmission = ""; + _report.psl = Psl; + transmission = ""; - bool US_CA = Apt_US_CA(ident); - if (!US_CA) { - // UK CAA radiotelephony manual indicates ATIS transmissions start - // with "This is ...", while US just starts with airport name. - transmission += This_is + " "; - } + // collect data and create report + createReport(apt); - // add facility name - genFacilityInfo(); + // add facility name + genFacilityInfo(); - if (_type == ATIS) { - // ATIS phraseology starts with "... airport information" - transmission += airport_information + " "; - } else { - // AWOS - transmission += Automated_weather_observation + " "; - } + if (_type == ATIS) { + // ATIS phraseology starts with "... airport information" + transmission += airport_information + " "; + } else { + // AWOS + transmission += Automated_weather_observation + " "; + } - string phonetic_seq_string = GetPhoneticLetter(sequence); // Add the sequence letter - transmission += phonetic_seq_string + BRK; + string phonetic_seq_string = GetPhoneticLetter(sequence); // Add the sequence letter + transmission += phonetic_seq_string + BRK; - genTimeInfo(); + genTimeInfo(); - genWindInfo(); + // some warnings may appear at the beginning + genWarnings(-1); - // Sounds better with a pause in there: - transmission += PAUSE; + if (_type == ATIS) // as opposed to AWOS + genRunwayInfo(apt); - genCloudInfo(); + // some warnings may appear after runway info + genWarnings(0); - double Tsl; - genTemperatureInfo(Tsl, US_CA); + // transition level + genTransitionLevel(apt); - genVisibilityInfo(); + // weather + if (!_report.concise) + transmission += Weather + BRK; - genPressureInfo(US_CA, Tsl); + genWindInfo(); - if (_type == ATIS /* as opposed to AWOS */) { - genRunwayInfo(phonetic_seq_string); - } + // clouds and visibility + { + string vis_info, cloud_info; + bool v = genVisibilityInfo(vis_info); + bool c = genCloudInfo(cloud_info); + _report.cavok = !(v || c); + if (!_report.cavok) + { + // there is some visibility or cloud restriction + transmission += vis_info + cloud_info; + } + else + { + // Abbreviation CAVOK vs full "clouds and visibility..." does not really depend on + // US vs rest of the world, it really seems to depend on the airport. Just use + // it as a heuristic. + if ((_report.US_CA)||(_report.concise)) + transmission += cav_ok + BRK; + else + transmission += clouds_and_visibility_OK + BRK; + } + } - // Pause in between two messages must be 3-5 seconds - transmission += PAUSE + PAUSE + PAUSE + PAUSE; - - ///////////////////////////////////////////////////////// - // postprocessing - ///////////////////////////////////////////////////////// - transmission_readable = transmission; - - // Take the previous readable string and munge it to - // be relatively-more acceptable to the primitive tts system. - // Note that : ; and . are among the token-delimiters recognized - // by the tts system. - for (size_t where;;) { - where = transmission.find_first_of(":."); - if (where == string::npos) break; - transmission.replace(where, 1, PAUSE); - } + // precipitation + genPrecipitationInfo(); + + // temperature + genTemperatureInfo(); + + // pressure + genPressureInfo(); + + // TODO check whether "no significant change" applies - somehow... + transmission += No_sig + BRK; // sounds better with festival than "nosig" + + // some warnings may appear at the very end + genWarnings(1); + + if ((!_report.concise)|| _report.US_CA) + transmission += Advise_on_initial_contact_you_have_information; + else + transmission += information; + transmission += " " + phonetic_seq_string + "."; - return 1; + if (!_report.US_CA) + { + // non-US ATIS ends with "out!" + transmission += " " + out; + } + + // Pause in between two messages must be 3-5 seconds + transmission += " / / / / / / / / "; + + ///////////////////////////////////////////////////////// + // post-processing + ///////////////////////////////////////////////////////// + transmission_readable = transmission; + + // Take the previous readable string and munge it to + // be relatively-more acceptable to the primitive tts system. + // Note that : ; and . are among the token-delimiters recognized + // by the tts system. + for (size_t where;;) { + where = transmission.find_first_of(":."); + if (where == string::npos) break; + transmission.replace(where, 1, PAUSE); + } + + return true; } -void FGATIS::genTimeInfo(void) +/** Collect (most of) the data and create report. + */ +void FGATIS::createReport(const FGAirport* apt) { - using namespace atmodel; - using namespace lex; + // check country + _report.US_CA = Apt_US_CA(ident); - string hours, mins; - string time_str = fgGetString("sim/time/gmt-string"); + // switch to enable brief ATIS message (really depends on the airport) + _report.concise = fgGetBool("/sim/atis/concise-reports", false); + + _report.ils = false; + // time information + string time_str = fgGetString("sim/time/gmt-string"); // Warning - this is fragile if the time string format changes - hours = time_str.substr(0,2).c_str(); - mins = time_str.substr(3,2).c_str(); + _report.hours = time_str.substr(0,2).c_str(); + _report.mins = time_str.substr(3,2).c_str(); - // speak each digit separately: - transmission += ConvertNumToSpokenDigits(hours + mins); - transmission += " " + zulu + " " + weather + BRK; + // pressure/temperature + { + double press, temp; + double Tsl = fgGetDouble("/environment/temperature-sea-level-degc"); + tie(press, temp) = PT_vs_hpt(_geod.getElevationM(), _report.psl*atmodel::inHg, Tsl + atmodel::freezing); + #if 0 + SG_LOG(SG_ATC, SG_ALERT, "Field P: " << press << " T: " << temp); + SG_LOG(SG_ATC, SG_ALERT, "based on elev " << elev + << " Psl: " << Psl + << " Tsl: " << Tsl); + #endif + _report.qnh = FGAtmo().QNH(_geod.getElevationM(), press); + _report.temp = int(SGMiscd::round(FGAtmo().fake_T_vs_a_us(_geod.getElevationFt(), Tsl))); + } + + // dew point + double dpsl = fgGetDouble("/environment/dewpoint-sea-level-degc"); + _report.dewpoint = int(SGMiscd::round(FGAtmo().fake_dp_vs_a_us(dpsl, _geod.getElevationFt()))); + + // precipitation + _report.rain_norm = fgGetDouble("environment/rain-norm"); + _report.snow_norm = fgGetDouble("environment/snow-norm"); + + // NOTAMs + _report.notam = 0; + if (fgGetBool("/sim/atis/random-notams", true)) + { + _report.notam = fgGetInt("/sim/atis/notam-id", 0); // fixed NOTAM for testing/debugging only + if (!_report.notam) + { + // select pseudo-random NOTAM (changes every hour, differs for each airport) + char cksum = 0; + string name = apt->getName(); + for(string::iterator p = name.begin(); p != name.end(); p++) + { + cksum += *p; + } + cksum ^= atoi(_report.hours.c_str()); + _report.notam = cksum % 12; // 12 intentionally higher than number of available NOTAMs, so they don't appear too often + // debugging + //fgSetInt("/sim/atis/selected-notam", _report.notam); + } + } } -void FGATIS::genVisibilityInfo(void) +void FGATIS::genPrecipitationInfo(void) { - using namespace atmodel; using namespace lex; - const int bs(100); - char buf[bs]; - transmission += Visibility + ": "; - double visibility = fgGetDouble("/environment/config/boundary/entry[0]/visibility-m"); - visibility /= atmodel::sm; // convert to statute miles - if (visibility < 0.25) { - transmission += less_than_one_quarter; - } else if (visibility < 0.5) { - transmission += one_quarter; - } else if (visibility < 0.75) { - transmission += one_half; - } else if (visibility < 1.0) { - transmission += three_quarters; - } else if (visibility >= 1.5 && visibility < 2.0) { - transmission += one_and_one_half; - } else { - // integer miles - if (visibility > 10) visibility = 10; - sprintf(buf, "%i", int(.5 + visibility)); - transmission += ConvertNumToSpokenDigits(buf); - } - transmission += BRK; + double rain_norm = _report.rain_norm; + double snow_norm = _report.snow_norm; + + // report rain or snow - which ever is worse + if (rain_norm > 0.7) + transmission += heavy + " " + rain + BRK; + else + if (snow_norm > 0.7) + transmission += heavy + " " + snow + BRK; + else + if (rain_norm > 0.4) + transmission += moderate + " " + rain + BRK; + else + if (snow_norm > 0.4) + transmission += moderate + " " + snow + BRK; + else + if (rain_norm > 0.2) + transmission += light + " " + rain + BRK; + else + if (snow_norm > 0.05) + transmission += light + " " + snow + BRK; + else + if (rain_norm > 0.05) + transmission += light + " " + drizzle + BRK; } -void FGATIS::genTemperatureInfo(double& Tsl, bool US_CA) +void FGATIS::genTimeInfo(void) { - using namespace atmodel; using namespace lex; - const int bs(100); - char buf[bs]; - transmission += Temperature + ": "; - Tsl = fgGetDouble("/environment/temperature-sea-level-degc"); - int temp = int(SGMiscd::round(FGAtmo().fake_T_vs_a_us(_geod.getElevationFt(), Tsl))); - if(temp < 0) { - transmission += lex::minus + " "; + if (!_report.concise) + transmission += Time + " "; + + // speak each digit separately: + transmission += ConvertNumToSpokenDigits(_report.hours + _report.mins); + transmission += " " + zulu + BRK; +} + +bool FGATIS::genVisibilityInfo(string& vis_info) +{ + using namespace lex; + + double visibility = fgGetDouble("/environment/config/boundary/entry[0]/visibility-m"); + bool IsMax = false; + bool USE_KM = !_report.US_CA; + + vis_info += Visibility + ": "; + if (USE_KM) + { + visibility /= 1000.0; // convert to statute miles + // integer kilometers + if (visibility >= 9.5) + { + visibility = 10; + IsMax = true; + } + snprintf(buf, sizeof(buf), "%i", int(.5 + visibility)); + // "kelometers" instead of "kilometers" since the festival language generator doesn't get it right otherwise + vis_info += ConvertNumToSpokenDigits(buf) + " " + kelometers; } - snprintf(buf, bs, "%i", abs(temp)); - transmission += ConvertNumToSpokenDigits(buf); - if (US_CA) transmission += " " + Celsius; - transmission += " " + dewpoint + " "; - double dpsl = fgGetDouble("/environment/dewpoint-sea-level-degc"); - temp = int(SGMiscd::round(FGAtmo().fake_dp_vs_a_us(dpsl, _geod.getElevationFt()))); - if(temp < 0) { + else + { + visibility /= atmodel::sm; // convert to statute miles + if (visibility < 0.25) { + vis_info += less_than_one_quarter; + } else if (visibility < 0.5) { + vis_info += one_quarter; + } else if (visibility < 0.75) { + vis_info += one_half; + } else if (visibility < 1.0) { + vis_info += three_quarters; + } else if (visibility >= 1.5 && visibility < 2.0) { + vis_info += one_and_one_half; + } else { + // integer miles + if (visibility > 9.5) + { + visibility = 10; + IsMax = true; + } + snprintf(buf, sizeof(buf), "%i", int(.5 + visibility)); + vis_info += ConvertNumToSpokenDigits(buf); + } + } + if (IsMax) + { + vis_info += " " + or_more; + } + vis_info += BRK; + return !IsMax; +} + +void FGATIS::addTemperature(int Temp) +{ + if (Temp < 0) transmission += lex::minus + " "; + else + if (Temp > 0) + { + transmission += lex::plus + " "; } - snprintf(buf, bs, "%i", abs(temp)); + snprintf(buf, sizeof(buf), "%i", abs(Temp)); transmission += ConvertNumToSpokenDigits(buf); - if (US_CA) transmission += " " + Celsius; + if (_report.US_CA) + transmission += " " + lex::Celsius; +} + +void FGATIS::genTemperatureInfo() +{ + // temperature + transmission += lex::Temperature + ": "; + addTemperature(_report.temp); + + // dewpoint + transmission += BRK + lex::Dewpoint + ": "; + addTemperature(_report.dewpoint); + transmission += BRK; } -void FGATIS::genCloudInfo(void) +bool FGATIS::genCloudInfo(string& cloud_info) { - using namespace atmodel; using namespace lex; - const int bs(100); - char buf[bs]; - bool did_some = false; bool did_ceiling = false; for (int layer = 0; layer <= 4; layer++) { - snprintf(buf, bs, "/environment/clouds/layer[%i]/coverage", layer); + snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/coverage", layer); string coverage = fgGetString(buf); - if (coverage == clear) continue; - snprintf(buf, bs, "/environment/clouds/layer[%i]/thickness-ft", layer); - if (fgGetDouble(buf) == 0) continue; - snprintf(buf, bs, "/environment/clouds/layer[%i]/elevation-ft", layer); + if (coverage == clear) + continue; + snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/thickness-ft", layer); + if (fgGetDouble(buf) == 0) + continue; + snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/elevation-ft", layer); double ceiling = int(fgGetDouble(buf) - _geod.getElevationFt()); - if (ceiling > 12000) continue; + if (ceiling > 12000) + continue; // BEWARE: At the present time, the environment system has no // way (so far as I know) to represent a "thin broken" or @@ -479,44 +657,56 @@ void FGATIS::genCloudInfo(void) // in the environment system, code will have to be written here // to handle them. - // First, do the prefix if any: + // First, do the prefix if any: if (coverage == scattered || coverage == few) { if (!did_some) { - transmission += " " + Sky_condition + ": "; - did_some++; + if (_report.concise) + cloud_info += Clouds + ": "; + else + cloud_info += Sky_condition + ": "; + did_some = true; } } else /* must be a ceiling */ if (!did_ceiling) { - transmission += " " + Ceiling + ": "; - did_ceiling++; - did_some++; + cloud_info += " " + Ceiling + ": "; + did_ceiling = true; + did_some = true; } else { - transmission += " "; // no prefix required + cloud_info += " "; // no prefix required } int cig00 = int(SGMiscd::round(ceiling/100)); // hundreds of feet if (cig00) { int cig000 = cig00/10; cig00 -= cig000*10; // just the hundreds digit if (cig000) { - snprintf(buf, bs, "%i", cig000); - transmission += ConvertNumToSpokenDigits(buf); - transmission += " " + thousand + " "; + snprintf(buf, sizeof(buf), "%i", cig000); + cloud_info += ConvertNumToSpokenDigits(buf); + cloud_info += " " + thousand + " "; } if (cig00) { - snprintf(buf, bs, "%i", cig00); - transmission += ConvertNumToSpokenDigits(buf); - transmission += " " + hundred + " "; + snprintf(buf, sizeof(buf), "%i", cig00); + cloud_info += ConvertNumToSpokenDigits(buf); + cloud_info += " " + hundred + " "; } } else { // Should this be "sky obscured?" - transmission += " " + zero + " "; // not "zero hundred" + cloud_info += " " + zero + " "; // not "zero hundred" } - transmission += coverage + BRK; + cloud_info += coverage + BRK; } - if (!did_some) transmission += " " + Sky + " " + clear + BRK; + if (!did_some) + cloud_info += " " + Sky + " " + clear + BRK; + return did_some; } void FGATIS::genFacilityInfo(void) { + if ((!_report.US_CA)&&(!_report.concise)) + { + // UK CAA radiotelephony manual indicates ATIS transmissions start + // with "This is ...", while US just starts with airport name. + transmission += lex::This_is + " "; + } + // SG_LOG(SG_ATC, SG_ALERT, "ATIS: facility name: " << name); // Note that at this point, multi-word facility names @@ -547,10 +737,9 @@ void FGATIS::genFacilityInfo(void) void FGATIS::genWindInfo(void) { - using namespace atmodel; using namespace lex; - transmission += wind + ": "; + transmission += Wind + ": "; double wind_speed = fgGetDouble("/environment/config/boundary/entry[0]/wind-speed-kt"); double wind_dir = fgGetDouble("/environment/config/boundary/entry[0]/wind-from-heading-deg"); @@ -570,91 +759,189 @@ void FGATIS::genWindInfo(void) // as the station environment anyway. wind_dir -= fgGetDouble("/environment/magnetic-variation-deg"); // wind_dir now magnetic if (wind_speed == 0) { - // Force west-facing rwys to be used in no-wind situations - // which is consistent with Flightgear's initial setup: + // Force west-facing rwys to be used in no-wind situations + // which is consistent with Flightgear's initial setup: wind_dir = 270; transmission += " " + light_and_variable; } else { - const int bs(100); - char buf[bs]; - // FIXME: get gust factor in somehow - snprintf(buf, bs, "%03.0f", 5*SGMiscd::round(wind_dir/5)); + snprintf(buf, sizeof(buf), "%03.0f", 5*SGMiscd::round(wind_dir/5)); transmission += ConvertNumToSpokenDigits(buf); + if (!_report.concise) + transmission += " " + degrees; + transmission += " "; + snprintf(buf, sizeof(buf), "%1.0f", wind_speed); + transmission += at + " " + ConvertNumToSpokenDigits(buf); + if (!_report.concise) + transmission += " " + knots; + } + transmission += BRK; +} - snprintf(buf, bs, "%1.0f", wind_speed); - transmission += " " + at + " " + ConvertNumToSpokenDigits(buf) + BRK; +void FGATIS::genTransitionLevel(const FGAirport* apt) +{ + double hPa = _report.qnh/atmodel::mbar; + + /* Transition level is the flight level above which aircraft must use standard pressure and below + * which airport pressure settings must be used. + * Following definitions are taken from German ATIS: + * QNH <= 977 hPa: TRL 80 + * QNH <= 1013 hPa: TRL 70 + * QNH > 1013 hPa: TRL 60 + * (maybe differs slightly for other countries...) + */ + int tl = 60; + if (hPa <= 977) + tl = 80; + else + if (hPa <= 1013) + tl = 70; + + // add an offset to the transition level for high altitude airports (just guessing here, + // seems reasonable) + double elevationFt = apt->getElevation(); + int e = int(elevationFt / 1000.0); + if (e >= 3) + { + // TL steps in 10(00)ft + tl += (e-2)*10; } + + snprintf(buf, sizeof(buf), "%02i", tl); + transmission += lex::Transition_level + ": " + ConvertNumToSpokenDigits(buf) + BRK; } -void FGATIS::genPressureInfo(bool US_CA, double Tsl) +void FGATIS::genPressureInfo(void) { - using namespace atmodel; using namespace lex; - double myQNH; - double Psl = fgGetDouble("/environment/pressure-sea-level-inhg"); - const int bs(100); - char buf[bs]; - { - double press, temp; - - tie(press, temp) = PT_vs_hpt(_geod.getElevationM(), Psl*inHg, Tsl + freezing); - #if 0 - SG_LOG(SG_ATC, SG_ALERT, "Field P: " << press << " T: " << temp); - SG_LOG(SG_ATC, SG_ALERT, "based on elev " << elev - << " Psl: " << Psl - << " Tsl: " << Tsl); - #endif - myQNH = FGAtmo().QNH(_geod.getElevationM(), press); - } - - // Convert to millibars for most of the world (not US, not CA) - if((!US_CA) && fgGetBool("/sim/atc/use-millibars")) { - transmission += QNH + ": "; - myQNH /= mbar; - snprintf(buf, bs, "%03.0f", myQNH); - transmission += ConvertNumToSpokenDigits(buf); - // TODO Extend voice samples so we can replace "millibars" with "hectopascal" (new ATIS standard since 2011) - if (myQNH < 1000) - transmission += " " + millibars; // "hectopascal" (millibars) spoken for values below 1000 only (to avoid confusion with inHg) - transmission += BRK; + // hectopascal for most of the world (not US, not CA) + if(!_report.US_CA) { + double hPa = _report.qnh/atmodel::mbar; + transmission += QNH + ": "; + snprintf(buf, sizeof(buf), "%03.0f", _report.qnh / atmodel::mbar); + transmission += ConvertNumToSpokenDigits(buf); + // "hectopascal" replaced "millibars" in new ATIS standard since 2011 + if ((!_report.concise)||(hPa < 1000)) + transmission += " " + hectopascal; // "hectopascal" must be provided for values below 1000 (to avoid confusion with inHg) + + // Many (European) airports (with lots of US traffic?) provide both, hPa and inHg announcements. + // Europeans keep the "decimal" in inHg readings to make the distinction to hPa even more apparent. + // hPa/inHg separated by "equals" or "or" with some airports + if (_report.concise) + transmission += " " + equals + " "; + else + transmission += " " + Or + " "; + snprintf(buf, sizeof(buf), "%04.2f", _report.qnh / atmodel::inHg); + transmission += ConvertNumToSpokenDigits(buf); + if (!_report.concise) + transmission += " " + inches; + transmission += BRK; } else { - transmission += Altimeter + ": "; - double asetting = myQNH / inHg; // use inches of mercury - asetting *= 100.; // shift two decimal places - snprintf(buf, bs, "%04.0f", asetting); - transmission += ConvertNumToSpokenDigits(buf) + BRK; + // use inches of mercury for US/CA + transmission += Altimeter + ": "; + double asetting = _report.qnh / atmodel::inHg; + // shift two decimal places, US/CA airports omit the "decimal" in inHg settings + asetting *= 100.; + snprintf(buf, sizeof(buf), "%04.0f", asetting); + transmission += ConvertNumToSpokenDigits(buf); } + + transmission += BRK; } -void FGATIS::genRunwayInfo(const string& phonetic_seq_string) +void FGATIS::genRunwayInfo(const FGAirport* apt) { - using namespace atmodel; using namespace lex; - const FGAirport* apt = fgFindAirportID(ident); - if (apt) { - FGRunway* rwy = apt->getActiveRunwayForUsage(); - if (rwy) - { - string rwy_no = rwy->ident(); - if(rwy_no != "NN") { - transmission += Landing_and_departing_runway + " "; - transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK; - #ifdef ATIS_TEST - if (msg_OK) { - msg_time = cur_time; - cout << "In atis.cxx, r.rwy_no: " << rwy_no - << " wind_dir: " << wind_dir << endl; + if (!apt) + return; + + FGRunway* rwy = apt->getActiveRunwayForUsage(); + if (!rwy) + return; + + string rwy_no = rwy->ident(); + if(rwy_no != "NN") + { + FGNavRecord* ils = rwy->ILS(); + if (ils) + { + _report.ils = true; + transmission += Expect_I_L_S_approach + " "+ runway + " "+ConvertRwyNumToSpokenString(rwy_no) + BRK; + if (fgGetBool("/sim/atis/announce-ils-frequency", false)) + { + // this is handy - but really non-standard (so disabled by default) + snprintf(buf, sizeof(buf), "%5.2f", ils->get_freq()/100.0); + transmission += I_L_S + " " + ConvertNumToSpokenDigits(buf) + BRK; } + } + else + { + transmission += Expect_visual_approach + " "+ runway + " "+ConvertRwyNumToSpokenString(rwy_no) + BRK; + } + + transmission += Landing_and_departing_runway + " "; + transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK; + #ifdef ATIS_TEST + if (msg_OK) { + msg_time = cur_time; + cout << "In atis.cxx, r.rwy_no: " << rwy_no + << " wind_dir: " << wind_dir << endl; + } #endif - } - } } - transmission += On_initial_contact_advise_you_have_information + " "; - transmission += phonetic_seq_string; - transmission += "... " + BRK; +} + +void FGATIS::genWarnings(int position) +{ + using namespace lex; + bool dayTime = (fgGetDouble("/sim/time/sun-angle-rad") < 1.57); + + if (position == -1) // warnings at beginning of ATIS + { + // bird related warnings at day-time only (birds are VFR-only! ;-) ) + if (dayTime) + { + if (_report.notam == 1) + transmission += Attention + ": " + flock_of_birds + " " + in_the_vicinity_of_the_airport + BRK; + else + if (_report.notam == 2) + transmission += Attention + ": " + bird_activity + " " + in_the_vicinity_of_the_airport + BRK; + } + } + else + if (position == 0) // warnings after runway messages + { + if ((_report.notam == 3)&&(_report.ils)) + { + // "__I_LS_" necessary to trick the language generator into pronouncing it properly + transmission += Attention + ": " + short_time__I_LS_interference_possible_by_taxiing_aircraft + BRK; + } + } + else + if (position == 1) // warnings at the end of the report + { + // "runway wet-wet-wet" warning in heavy rain + if (_report.rain_norm > 0.6) + { + // "wet" is repeated 3 times in ATIS warnings, since the word is difficult + // to understand over radio - but the message is important. + transmission += runway_wet + " " + wet + " " + wet + BRK; + } + + if (_report.notam == 4) + { + // intentional: "reed" instead of "read" since festival gets it wrong otherwise + transmission += reed_back_all_runway_hold_instructions + BRK; + } + else + if ((_report.notam == 5)&& _report.cavok && dayTime && + (_report.rain_norm == 0) && (_report.snow_norm == 0)) // ;-) + { + transmission += Attention + ": " + glider_operation_in_sector + BRK; + } + } } // Put the transmission into the property tree. @@ -663,7 +950,7 @@ void FGATIS::genRunwayInfo(const string& phonetic_seq_string) // http://localhost:5400/instrumentation/comm[1] // // (Also, if in debug mode, dump it to the console.) -void FGATIS::TreeOut(int msg_OK) +void FGATIS::treeOut(int msg_OK) { _atis->setStringValue("
\n" + transmission_readable + "
\n"); SG_LOG(SG_ATC, SG_DEBUG, "**** ATIS active on: " << _name << @@ -671,7 +958,6 @@ void FGATIS::TreeOut(int msg_OK) } - class RangeFilter : public CommStation::Filter { public: @@ -713,7 +999,7 @@ private: }; // Search for ATC stations by frequency -void FGATIS::search(void) +bool FGATIS::search(double dt) { double frequency = _freq->getDoubleValue(); @@ -721,9 +1007,12 @@ void FGATIS::search(void) // in order to be consistent with apt.dat et cetera. int freqKhz = 10 * static_cast(frequency * 100 + 0.25); + // only search tuned frequencies when necessary + _time_before_search_sec -= dt; + // throttle frequency searches if ((freqKhz == _last_frequency)&&(_time_before_search_sec > 0)) - return; + return false; _last_frequency = freqKhz; _time_before_search_sec = 4.0; @@ -744,4 +1033,6 @@ void FGATIS::search(void) { SG_LOG(SG_ATC, SG_DEBUG, "FGATIS " << _name << ": no station."); } + + return true; } diff --git a/src/ATCDCL/atis.hxx b/src/ATCDCL/atis.hxx index 6f753d659..525001fa0 100644 --- a/src/ATCDCL/atis.hxx +++ b/src/ATCDCL/atis.hxx @@ -19,7 +19,6 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - #ifndef _FG_ATIS_HXX #define _FG_ATIS_HXX @@ -32,6 +31,8 @@ #include "ATC.hxx" +class FGAirport; + typedef std::map MSS; class FGATIS : public FGATC { @@ -70,6 +71,7 @@ class FGATIS : public FGATC { time_t cur_time; int msg_OK; bool _attention; + bool _check_transmission; bool _prev_display; // Previous value of _display flag MSS _remap; // abbreviations to be expanded @@ -78,11 +80,33 @@ class FGATIS : public FGATC { double _time_before_search_sec; int _last_frequency; + // temporary buffer for string conversions + char buf[100]; + + // data for the current ATIS report + struct + { + std::string phonetic_seq_string; + bool US_CA; + bool cavok; + bool concise; + bool ils; + int temp; + int dewpoint; + double psl; + double qnh; + double rain_norm, snow_norm; + int notam; + std::string hours,mins; + } _report; + public: FGATIS(const std::string& name, int num); - virtual void init(); + void init(); + void reinit(); + void attend(SGPropertyNode* node); //run the ATIS instance @@ -96,27 +120,32 @@ protected: private: - /** generate the ATIS transmission text */ - int genTransmission (const int regen, const bool special); + void createReport (const FGAirport* apt); + /** generate the ATIS transmission text */ + bool genTransmission (const int regen, bool forceUpdate); void genTimeInfo (void); void genFacilityInfo (void); - void genVisibilityInfo (void); - void genCloudInfo (void); + void genPrecipitationInfo(void); + bool genVisibilityInfo (std::string& vis_info); + bool genCloudInfo (std::string& cloud_info); void genWindInfo (void); - void genTemperatureInfo (double& Tsl, bool US_CA); - void genPressureInfo (bool US_CA, double Tsl); - void genRunwayInfo (const std::string& phonetic_seq_string); + void genTemperatureInfo (void); + void genTransitionLevel (const FGAirport* apt); + void genPressureInfo (void); + void genRunwayInfo (const FGAirport* apt); + void genWarnings (int position); + + void addTemperature (int Temp); // Put the text into the property tree // (and in debug mode, print it on the console): - void TreeOut(int msgOK); + void treeOut(int msgOK); // Search the specified radio for stations on the same frequency and in range. - void search(void); + bool search(double dt); friend std::istream& operator>> ( std::istream&, FGATIS& ); - }; typedef int (FGATIS::*int_getter)() const; diff --git a/src/ATCDCL/atis_lexicon.hxx b/src/ATCDCL/atis_lexicon.hxx index d201674ff..ef6f02735 100644 --- a/src/ATCDCL/atis_lexicon.hxx +++ b/src/ATCDCL/atis_lexicon.hxx @@ -15,11 +15,11 @@ Q(Airfield) Q(Airbase) Q(Junior) Q(Celsius) -Q(wind) +Q(Wind) Q(zulu) Q(zulu_weather) Q(Automated_weather_observation) -Q(weather) +Q(Weather) Q(airport_information) Q(International) Q(Regional) @@ -45,9 +45,10 @@ Q(overcast) Q(thin) Q(Sky_condition) Q(Sky) +Q(Clouds) Q(Ceiling) Q(minus) -Q(dewpoint) +Q(Dewpoint) Q(Visibility) Q(less_than_one_quarter) Q(one_quarter) @@ -56,13 +57,66 @@ Q(three_quarters) Q(one_and_one_half) Q(Altimeter) Q(QNH) -Q(millibars) Q(Landing_and_departing_runway) -Q(On_initial_contact_advise_you_have_information) +Q(Advise_on_initial_contact_you_have_information) Q(This_is) +Q(information) +Q(millibars) +Q(hectopascal) +Q(inches) +Q(I_L_S) +Q(visual) +Q(cav_ok) +Q(clouds_and_visibility_OK) +Q(out) +Q(equals) +Q(Expect_I_L_S_approach) +Q(Expect_visual_approach) +Q(Transition_level) +Q(No_sig) +Q(Time) +Q(kelometers) +Q(Attention) +Q(flock_of_birds) +Q(bird_activity) +Q(in_the_vicinity_of_the_airport) +Q(short_time__I_LS_interference_possible_by_taxiing_aircraft) +Q(reed_back_all_runway_hold_instructions) +Q(glider_operation_in_sector) +Q(airport) +Q(runway_wet) +Q(runway_in_use) +Q(arrivals) +Q(runway) +Q(runways) +Q(expect) +Q(approach) +Q(departures) +Q(wet) +Q(ice) +Q(closed) +Q(light) +Q(moderate) +Q(heavy) +Q(rain) +Q(drizzle) +Q(snow) +Q(fog) +Q(plus) +Q(hours) +Q(variable) +Q(from) +Q(Or) +Q(And) +Q(to) +Q(maximum) +Q(between) +Q(degrees) +Q(or_more) Q(left) Q(right) Q(center) +Q(knots) } #undef Q diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index 9e8c4d83b..f7c591d39 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -841,6 +841,8 @@ void fgReInitSubsystems() globals->get_subsystem("systems")->reinit(); globals->get_subsystem("instrumentation")->reinit(); + globals->get_subsystem("ATIS")->reinit(); + // setup state to end re-init fgSetBool("/sim/signals/reinit", false); if ( !freeze ) { -- 2.39.5