1
0
Fork 0

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.
This commit is contained in:
ThorstenB 2012-10-13 16:48:56 +02:00
parent 3bf09215b0
commit 32b9c61528
4 changed files with 651 additions and 275 deletions

View file

@ -2,6 +2,7 @@
// This is the implementation of the FGATIS class // This is the implementation of the FGATIS class
// //
// Written by David Luff, started October 2001. // Written by David Luff, started October 2001.
// Extended by Thorsten Brehm, October 2012.
// //
// Copyright (C) 2001 David C Luff - david.luff@nottingham.ac.uk // Copyright (C) 2001 David C Luff - david.luff@nottingham.ac.uk
// //
@ -36,6 +37,7 @@
#include <simgear/compiler.h> #include <simgear/compiler.h>
#include <simgear/math/sg_random.h> #include <simgear/math/sg_random.h>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <stdlib.h> // atoi() #include <stdlib.h> // atoi()
#include <stdio.h> // sprintf #include <stdio.h> // sprintf
@ -56,6 +58,7 @@
#include <Airports/dynamics.hxx> #include <Airports/dynamics.hxx>
#include <ATC/CommStation.hxx> #include <ATC/CommStation.hxx>
#include <Navaids/navrecord.hxx>
#include "ATCutils.hxx" #include "ATCutils.hxx"
#include "ATISmgr.hxx" #include "ATISmgr.hxx"
@ -80,6 +83,7 @@ FGATIS::FGATIS(const std::string& name, int num) :
cur_time(0), cur_time(0),
msg_OK(0), msg_OK(0),
_attention(false), _attention(false),
_check_transmission(true),
_prev_display(0), _prev_display(0),
_time_before_search_sec(0), _time_before_search_sec(0),
_last_frequency(0) _last_frequency(0)
@ -117,15 +121,18 @@ FGATIS::FGATIS(const std::string& name, int num) :
// //
// Load the remap list from the .hxx file: // Load the remap list from the .hxx file:
using namespace lex; using namespace lex;
# define NIL ""
# define REMAP(from,to) _remap[#from] = to;
# include "atis_remap.hxx"
# undef REMAP
# undef NIL
#ifdef ATIS_TEST # 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"); SG_LOG(SG_ATC, SG_ALERT, "ATIS initialized");
#endif #endif
_report.psl = 0;
} }
// Hint: // Hint:
@ -143,14 +150,22 @@ FGATCVoice* FGATIS::GetVoicePointer()
return pAtisMgr->GetVoicePointer(ATIS); return pAtisMgr->GetVoicePointer(ATIS);
} }
void FGATIS::init() { void FGATIS::init()
{
// Nothing to see here. Move along. // Nothing to see here. Move along.
} }
void FGATIS::reinit()
{
_time_before_search_sec = 0;
_check_transmission = true;
}
void void
FGATIS::attend(SGPropertyNode* node) FGATIS::attend(SGPropertyNode* node)
{ {
_attention = node->getBoolValue(); if (node->getBoolValue())
_attention = true;
#ifdef ATMO_TEST #ifdef ATMO_TEST
int flag = fgGetInt("/sim/logging/atmo"); int flag = fgGetInt("/sim/logging/atmo");
if (flag) { if (flag) {
@ -179,13 +194,11 @@ void FGATIS::update(double dt) {
double volume = 0; double volume = 0;
if ((_electrical->getDoubleValue() > 8) && _serviceable->getBoolValue()) if ((_electrical->getDoubleValue() > 8) && _serviceable->getBoolValue())
{ {
_time_before_search_sec -= dt;
// radio is switched on and OK // radio is switched on and OK
if (_operable.valid()) if (_operable.valid())
_operable->setBoolValue(true); _operable->setBoolValue(true);
// Search the tuned frequencies _check_transmission |= search(dt);
search();
if (_display) if (_display)
{ {
@ -202,14 +215,21 @@ void FGATIS::update(double dt) {
if (volume > 0.05) if (volume > 0.05)
{ {
bool changed = false;
if (_check_transmission)
{
_check_transmission = false;
// Check if we need to update the message // Check if we need to update the message
// - basically every hour and if the weather changes significantly at the station // - 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 // If !_prev_display, the radio had been detuned for a while and our
// "transmission" variable was lost when we were de-instantiated. // "transmission" variable was lost when we were de-instantiated.
int changed = genTransmission(!_prev_display, _attention); if (genTransmission(!_prev_display, _attention))
{
// update output property // update output property
TreeOut(msg_OK); treeOut(msg_OK);
changed = true;
}
}
if (changed || volume != old_volume) { if (changed || volume != old_volume) {
// audio output enabled // audio output enabled
@ -227,14 +247,6 @@ void FGATIS::update(double dt) {
FGATC::update(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. // Replace all occurrences of a given word.
// Words in the original string must be separated by hyphens (not spaces). // 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. // 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(); where += nnn.length();
} }
www = uppercase(www); www = simgear::strutils::uppercase(www);
for ( ; (where = orig.find(www, where)) != string::npos ; ) { for ( ; (where = orig.find(www, where)) != string::npos ; ) {
orig.replace(where, www.length(), nnn); orig.replace(where, www.length(), nnn);
where += nnn.length(); where += nnn.length();
@ -278,9 +290,10 @@ const int minute(60); // measured in seconds
// ascertaining which airports are in the US, let alone // ascertaining which airports are in the US, let alone
// (b) ascertaining which other places use inches. // (b) ascertaining which other places use inches.
// //
bool Apt_US_CA(const string id) { bool Apt_US_CA(const string id)
// Assume all IDs have length 3 or 4. {
// No counterexamples have been seen. // Assume all IDs have length 3 or 4.
// No counterexamples have been seen.
if (id.length() == 4) { if (id.length() == 4) {
if (id.substr(0,1) == "K") return true; if (id.substr(0,1) == "K") return true;
if (id.substr(0,2) == "CY") return true; if (id.substr(0,2) == "CY") return true;
@ -291,37 +304,40 @@ bool Apt_US_CA(const string id) {
return false; return false;
} }
static string BRK = ".\n"; // voice spacers
static string PAUSE = " / "; static const string BRK = ".\n";
static const string PAUSE = " / ";
/** Generate the actual broadcast ATIS transmission. /** Generate the actual broadcast ATIS transmission.
* 'regen' triggers a regeneration of the /current/ 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. * Returns 1 if we actually generated something.
*/ */
int FGATIS::genTransmission(const int regen, const bool special) { bool FGATIS::genTransmission(const int regen, bool forceUpdate)
using namespace atmodel; {
using namespace lex; using namespace lex;
// ATIS updated hourly, AWOS updated more frequently // ATIS updated hourly, AWOS updated more frequently
int interval = _type == ATIS ? ATIS_interval : 2*minute; int interval = _type == ATIS ? ATIS_interval : 2*minute;
// 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); FGAirport* apt = FGAirport::findByIdent(ident);
int sequence = apt->getDynamics()->updateAtisSequence(interval, special); int sequence = apt->getDynamics()->updateAtisSequence(interval, forceUpdate);
if (!regen && sequence > LTRS) { if (!regen && sequence > LTRS) {
//xx if (msg_OK) cout << "ATIS: no change: " << sequence << endl; //xx if (msg_OK) cout << "ATIS: no change: " << sequence << endl;
//xx msg_time = cur_time; //xx msg_time = cur_time;
return 0; // no change since last time return false; // no change since last time
} }
_report.psl = Psl;
transmission = ""; transmission = "";
bool US_CA = Apt_US_CA(ident); // collect data and create report
if (!US_CA) { createReport(apt);
// UK CAA radiotelephony manual indicates ATIS transmissions start
// with "This is ...", while US just starts with airport name.
transmission += This_is + " ";
}
// add facility name // add facility name
genFacilityInfo(); genFacilityInfo();
@ -339,29 +355,79 @@ int FGATIS::genTransmission(const int regen, const bool special) {
genTimeInfo(); genTimeInfo();
// some warnings may appear at the beginning
genWarnings(-1);
if (_type == ATIS) // as opposed to AWOS
genRunwayInfo(apt);
// some warnings may appear after runway info
genWarnings(0);
// transition level
genTransitionLevel(apt);
// weather
if (!_report.concise)
transmission += Weather + BRK;
genWindInfo(); genWindInfo();
// Sounds better with a pause in there: // clouds and visibility
transmission += PAUSE; {
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;
}
}
genCloudInfo(); // precipitation
genPrecipitationInfo();
double Tsl; // temperature
genTemperatureInfo(Tsl, US_CA); genTemperatureInfo();
genVisibilityInfo(); // pressure
genPressureInfo();
genPressureInfo(US_CA, Tsl); // TODO check whether "no significant change" applies - somehow...
transmission += No_sig + BRK; // sounds better with festival than "nosig"
if (_type == ATIS /* as opposed to AWOS */) { // some warnings may appear at the very end
genRunwayInfo(phonetic_seq_string); genWarnings(1);
if ((!_report.concise)|| _report.US_CA)
transmission += Advise_on_initial_contact_you_have_information;
else
transmission += information;
transmission += " " + phonetic_seq_string + ".";
if (!_report.US_CA)
{
// non-US ATIS ends with "out!"
transmission += " " + out;
} }
// Pause in between two messages must be 3-5 seconds // Pause in between two messages must be 3-5 seconds
transmission += PAUSE + PAUSE + PAUSE + PAUSE; transmission += " / / / / / / / / ";
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
// postprocessing // post-processing
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
transmission_readable = transmission; transmission_readable = transmission;
@ -375,103 +441,215 @@ int FGATIS::genTransmission(const int regen, const bool special) {
transmission.replace(where, 1, PAUSE); transmission.replace(where, 1, PAUSE);
} }
return 1; return true;
}
/** Collect (most of) the data and create report.
*/
void FGATIS::createReport(const FGAirport* apt)
{
// check country
_report.US_CA = Apt_US_CA(ident);
// 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
_report.hours = time_str.substr(0,2).c_str();
_report.mins = time_str.substr(3,2).c_str();
// 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::genPrecipitationInfo(void)
{
using namespace lex;
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::genTimeInfo(void) void FGATIS::genTimeInfo(void)
{ {
using namespace atmodel;
using namespace lex; using namespace lex;
string hours, mins; if (!_report.concise)
string time_str = fgGetString("sim/time/gmt-string"); transmission += Time + " ";
// 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();
// speak each digit separately: // speak each digit separately:
transmission += ConvertNumToSpokenDigits(hours + mins); transmission += ConvertNumToSpokenDigits(_report.hours + _report.mins);
transmission += " " + zulu + " " + weather + BRK; transmission += " " + zulu + BRK;
} }
void FGATIS::genVisibilityInfo(void) bool FGATIS::genVisibilityInfo(string& vis_info)
{ {
using namespace atmodel;
using namespace lex; using namespace lex;
const int bs(100);
char buf[bs];
transmission += Visibility + ": ";
double visibility = fgGetDouble("/environment/config/boundary/entry[0]/visibility-m"); 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;
}
else
{
visibility /= atmodel::sm; // convert to statute miles visibility /= atmodel::sm; // convert to statute miles
if (visibility < 0.25) { if (visibility < 0.25) {
transmission += less_than_one_quarter; vis_info += less_than_one_quarter;
} else if (visibility < 0.5) { } else if (visibility < 0.5) {
transmission += one_quarter; vis_info += one_quarter;
} else if (visibility < 0.75) { } else if (visibility < 0.75) {
transmission += one_half; vis_info += one_half;
} else if (visibility < 1.0) { } else if (visibility < 1.0) {
transmission += three_quarters; vis_info += three_quarters;
} else if (visibility >= 1.5 && visibility < 2.0) { } else if (visibility >= 1.5 && visibility < 2.0) {
transmission += one_and_one_half; vis_info += one_and_one_half;
} else { } else {
// integer miles // integer miles
if (visibility > 10) visibility = 10; if (visibility > 9.5)
sprintf(buf, "%i", int(.5 + visibility)); {
transmission += ConvertNumToSpokenDigits(buf); 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, sizeof(buf), "%i", abs(Temp));
transmission += ConvertNumToSpokenDigits(buf);
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; transmission += BRK;
} }
void FGATIS::genTemperatureInfo(double& Tsl, bool US_CA) bool FGATIS::genCloudInfo(string& cloud_info)
{ {
using namespace atmodel;
using namespace lex; 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 + " ";
}
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) {
transmission += lex::minus + " ";
}
snprintf(buf, bs, "%i", abs(temp));
transmission += ConvertNumToSpokenDigits(buf);
if (US_CA) transmission += " " + Celsius;
transmission += BRK;
}
void FGATIS::genCloudInfo(void)
{
using namespace atmodel;
using namespace lex;
const int bs(100);
char buf[bs];
bool did_some = false; bool did_some = false;
bool did_ceiling = false; bool did_ceiling = false;
for (int layer = 0; layer <= 4; layer++) { 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); string coverage = fgGetString(buf);
if (coverage == clear) continue; if (coverage == clear)
snprintf(buf, bs, "/environment/clouds/layer[%i]/thickness-ft", layer); continue;
if (fgGetDouble(buf) == 0) continue; snprintf(buf, sizeof(buf), "/environment/clouds/layer[%i]/thickness-ft", layer);
snprintf(buf, bs, "/environment/clouds/layer[%i]/elevation-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()); 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 // BEWARE: At the present time, the environment system has no
// way (so far as I know) to represent a "thin broken" or // way (so far as I know) to represent a "thin broken" or
@ -482,41 +660,53 @@ void FGATIS::genCloudInfo(void)
// First, do the prefix if any: // First, do the prefix if any:
if (coverage == scattered || coverage == few) { if (coverage == scattered || coverage == few) {
if (!did_some) { if (!did_some) {
transmission += " " + Sky_condition + ": "; if (_report.concise)
did_some++; cloud_info += Clouds + ": ";
else
cloud_info += Sky_condition + ": ";
did_some = true;
} }
} else /* must be a ceiling */ if (!did_ceiling) { } else /* must be a ceiling */ if (!did_ceiling) {
transmission += " " + Ceiling + ": "; cloud_info += " " + Ceiling + ": ";
did_ceiling++; did_ceiling = true;
did_some++; did_some = true;
} else { } else {
transmission += " "; // no prefix required cloud_info += " "; // no prefix required
} }
int cig00 = int(SGMiscd::round(ceiling/100)); // hundreds of feet int cig00 = int(SGMiscd::round(ceiling/100)); // hundreds of feet
if (cig00) { if (cig00) {
int cig000 = cig00/10; int cig000 = cig00/10;
cig00 -= cig000*10; // just the hundreds digit cig00 -= cig000*10; // just the hundreds digit
if (cig000) { if (cig000) {
snprintf(buf, bs, "%i", cig000); snprintf(buf, sizeof(buf), "%i", cig000);
transmission += ConvertNumToSpokenDigits(buf); cloud_info += ConvertNumToSpokenDigits(buf);
transmission += " " + thousand + " "; cloud_info += " " + thousand + " ";
} }
if (cig00) { if (cig00) {
snprintf(buf, bs, "%i", cig00); snprintf(buf, sizeof(buf), "%i", cig00);
transmission += ConvertNumToSpokenDigits(buf); cloud_info += ConvertNumToSpokenDigits(buf);
transmission += " " + hundred + " "; cloud_info += " " + hundred + " ";
} }
} else { } else {
// Should this be "sky obscured?" // 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) 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); // SG_LOG(SG_ATC, SG_ALERT, "ATIS: facility name: " << name);
// Note that at this point, multi-word facility names // Note that at this point, multi-word facility names
@ -547,10 +737,9 @@ void FGATIS::genFacilityInfo(void)
void FGATIS::genWindInfo(void) void FGATIS::genWindInfo(void)
{ {
using namespace atmodel;
using namespace lex; using namespace lex;
transmission += wind + ": "; transmission += Wind + ": ";
double wind_speed = fgGetDouble("/environment/config/boundary/entry[0]/wind-speed-kt"); 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"); double wind_dir = fgGetDouble("/environment/config/boundary/entry[0]/wind-from-heading-deg");
@ -575,71 +764,123 @@ void FGATIS::genWindInfo(void)
wind_dir = 270; wind_dir = 270;
transmission += " " + light_and_variable; transmission += " " + light_and_variable;
} else { } else {
const int bs(100);
char buf[bs];
// FIXME: get gust factor in somehow // 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); transmission += ConvertNumToSpokenDigits(buf);
if (!_report.concise)
snprintf(buf, bs, "%1.0f", wind_speed); transmission += " " + degrees;
transmission += " " + at + " " + ConvertNumToSpokenDigits(buf) + BRK; transmission += " ";
snprintf(buf, sizeof(buf), "%1.0f", wind_speed);
transmission += at + " " + ConvertNumToSpokenDigits(buf);
if (!_report.concise)
transmission += " " + knots;
} }
transmission += BRK;
} }
void FGATIS::genPressureInfo(bool US_CA, double Tsl) void FGATIS::genTransitionLevel(const FGAirport* apt)
{ {
using namespace atmodel; double hPa = _report.qnh/atmodel::mbar;
using namespace lex;
double myQNH;
double Psl = fgGetDouble("/environment/pressure-sea-level-inhg");
const int bs(100);
char buf[bs];
/* 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)
{ {
double press, temp; // TL steps in 10(00)ft
tl += (e-2)*10;
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) snprintf(buf, sizeof(buf), "%02i", tl);
if((!US_CA) && fgGetBool("/sim/atc/use-millibars")) { transmission += lex::Transition_level + ": " + ConvertNumToSpokenDigits(buf) + BRK;
}
void FGATIS::genPressureInfo(void)
{
using namespace lex;
// hectopascal for most of the world (not US, not CA)
if(!_report.US_CA) {
double hPa = _report.qnh/atmodel::mbar;
transmission += QNH + ": "; transmission += QNH + ": ";
myQNH /= mbar; snprintf(buf, sizeof(buf), "%03.0f", _report.qnh / atmodel::mbar);
snprintf(buf, bs, "%03.0f", myQNH);
transmission += ConvertNumToSpokenDigits(buf); transmission += ConvertNumToSpokenDigits(buf);
// TODO Extend voice samples so we can replace "millibars" with "hectopascal" (new ATIS standard since 2011) // "hectopascal" replaced "millibars" in new ATIS standard since 2011
if (myQNH < 1000) if ((!_report.concise)||(hPa < 1000))
transmission += " " + millibars; // "hectopascal" (millibars) spoken for values below 1000 only (to avoid confusion with inHg) 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; transmission += BRK;
} else { } else {
// use inches of mercury for US/CA
transmission += Altimeter + ": "; transmission += Altimeter + ": ";
double asetting = myQNH / inHg; // use inches of mercury double asetting = _report.qnh / atmodel::inHg;
asetting *= 100.; // shift two decimal places // shift two decimal places, US/CA airports omit the "decimal" in inHg settings
snprintf(buf, bs, "%04.0f", asetting); asetting *= 100.;
transmission += ConvertNumToSpokenDigits(buf) + BRK; 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; using namespace lex;
const FGAirport* apt = fgFindAirportID(ident); if (!apt)
if (apt) { return;
FGRunway* rwy = apt->getActiveRunwayForUsage(); FGRunway* rwy = apt->getActiveRunwayForUsage();
if (rwy) if (!rwy)
{ return;
string rwy_no = rwy->ident(); string rwy_no = rwy->ident();
if(rwy_no != "NN") { 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 += Landing_and_departing_runway + " ";
transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK; transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK;
#ifdef ATIS_TEST #ifdef ATIS_TEST
@ -650,11 +891,57 @@ void FGATIS::genRunwayInfo(const string& phonetic_seq_string)
} }
#endif #endif
} }
}
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;
} }
} }
transmission += On_initial_contact_advise_you_have_information + " ";
transmission += phonetic_seq_string;
transmission += "... " + BRK;
} }
// Put the transmission into the property tree. // 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] // http://localhost:5400/instrumentation/comm[1]
// //
// (Also, if in debug mode, dump it to the console.) // (Also, if in debug mode, dump it to the console.)
void FGATIS::TreeOut(int msg_OK) void FGATIS::treeOut(int msg_OK)
{ {
_atis->setStringValue("<pre>\n" + transmission_readable + "</pre>\n"); _atis->setStringValue("<pre>\n" + transmission_readable + "</pre>\n");
SG_LOG(SG_ATC, SG_DEBUG, "**** ATIS active on: " << _name << 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 class RangeFilter : public CommStation::Filter
{ {
public: public:
@ -713,7 +999,7 @@ private:
}; };
// Search for ATC stations by frequency // Search for ATC stations by frequency
void FGATIS::search(void) bool FGATIS::search(double dt)
{ {
double frequency = _freq->getDoubleValue(); double frequency = _freq->getDoubleValue();
@ -721,9 +1007,12 @@ void FGATIS::search(void)
// in order to be consistent with apt.dat et cetera. // in order to be consistent with apt.dat et cetera.
int freqKhz = 10 * static_cast<int>(frequency * 100 + 0.25); int freqKhz = 10 * static_cast<int>(frequency * 100 + 0.25);
// only search tuned frequencies when necessary
_time_before_search_sec -= dt;
// throttle frequency searches // throttle frequency searches
if ((freqKhz == _last_frequency)&&(_time_before_search_sec > 0)) if ((freqKhz == _last_frequency)&&(_time_before_search_sec > 0))
return; return false;
_last_frequency = freqKhz; _last_frequency = freqKhz;
_time_before_search_sec = 4.0; _time_before_search_sec = 4.0;
@ -744,4 +1033,6 @@ void FGATIS::search(void)
{ {
SG_LOG(SG_ATC, SG_DEBUG, "FGATIS " << _name << ": no station."); SG_LOG(SG_ATC, SG_DEBUG, "FGATIS " << _name << ": no station.");
} }
return true;
} }

View file

@ -19,7 +19,6 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef _FG_ATIS_HXX #ifndef _FG_ATIS_HXX
#define _FG_ATIS_HXX #define _FG_ATIS_HXX
@ -32,6 +31,8 @@
#include "ATC.hxx" #include "ATC.hxx"
class FGAirport;
typedef std::map<std::string,std::string> MSS; typedef std::map<std::string,std::string> MSS;
class FGATIS : public FGATC { class FGATIS : public FGATC {
@ -70,6 +71,7 @@ class FGATIS : public FGATC {
time_t cur_time; time_t cur_time;
int msg_OK; int msg_OK;
bool _attention; bool _attention;
bool _check_transmission;
bool _prev_display; // Previous value of _display flag bool _prev_display; // Previous value of _display flag
MSS _remap; // abbreviations to be expanded MSS _remap; // abbreviations to be expanded
@ -78,11 +80,33 @@ class FGATIS : public FGATC {
double _time_before_search_sec; double _time_before_search_sec;
int _last_frequency; 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: public:
FGATIS(const std::string& name, int num); FGATIS(const std::string& name, int num);
virtual void init(); void init();
void reinit();
void attend(SGPropertyNode* node); void attend(SGPropertyNode* node);
//run the ATIS instance //run the ATIS instance
@ -96,27 +120,32 @@ protected:
private: private:
/** generate the ATIS transmission text */ void createReport (const FGAirport* apt);
int genTransmission (const int regen, const bool special);
/** generate the ATIS transmission text */
bool genTransmission (const int regen, bool forceUpdate);
void genTimeInfo (void); void genTimeInfo (void);
void genFacilityInfo (void); void genFacilityInfo (void);
void genVisibilityInfo (void); void genPrecipitationInfo(void);
void genCloudInfo (void); bool genVisibilityInfo (std::string& vis_info);
bool genCloudInfo (std::string& cloud_info);
void genWindInfo (void); void genWindInfo (void);
void genTemperatureInfo (double& Tsl, bool US_CA); void genTemperatureInfo (void);
void genPressureInfo (bool US_CA, double Tsl); void genTransitionLevel (const FGAirport* apt);
void genRunwayInfo (const std::string& phonetic_seq_string); void genPressureInfo (void);
void genRunwayInfo (const FGAirport* apt);
void genWarnings (int position);
void addTemperature (int Temp);
// Put the text into the property tree // Put the text into the property tree
// (and in debug mode, print it on the console): // (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. // 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& ); friend std::istream& operator>> ( std::istream&, FGATIS& );
}; };
typedef int (FGATIS::*int_getter)() const; typedef int (FGATIS::*int_getter)() const;

View file

@ -15,11 +15,11 @@ Q(Airfield)
Q(Airbase) Q(Airbase)
Q(Junior) Q(Junior)
Q(Celsius) Q(Celsius)
Q(wind) Q(Wind)
Q(zulu) Q(zulu)
Q(zulu_weather) Q(zulu_weather)
Q(Automated_weather_observation) Q(Automated_weather_observation)
Q(weather) Q(Weather)
Q(airport_information) Q(airport_information)
Q(International) Q(International)
Q(Regional) Q(Regional)
@ -45,9 +45,10 @@ Q(overcast)
Q(thin) Q(thin)
Q(Sky_condition) Q(Sky_condition)
Q(Sky) Q(Sky)
Q(Clouds)
Q(Ceiling) Q(Ceiling)
Q(minus) Q(minus)
Q(dewpoint) Q(Dewpoint)
Q(Visibility) Q(Visibility)
Q(less_than_one_quarter) Q(less_than_one_quarter)
Q(one_quarter) Q(one_quarter)
@ -56,13 +57,66 @@ Q(three_quarters)
Q(one_and_one_half) Q(one_and_one_half)
Q(Altimeter) Q(Altimeter)
Q(QNH) Q(QNH)
Q(millibars)
Q(Landing_and_departing_runway) 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(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(left)
Q(right) Q(right)
Q(center) Q(center)
Q(knots)
} }
#undef Q #undef Q

View file

@ -841,6 +841,8 @@ void fgReInitSubsystems()
globals->get_subsystem("systems")->reinit(); globals->get_subsystem("systems")->reinit();
globals->get_subsystem("instrumentation")->reinit(); globals->get_subsystem("instrumentation")->reinit();
globals->get_subsystem("ATIS")->reinit();
// setup state to end re-init // setup state to end re-init
fgSetBool("/sim/signals/reinit", false); fgSetBool("/sim/signals/reinit", false);
if ( !freeze ) { if ( !freeze ) {