diff --git a/src/ATC/AIMgr.cxx b/src/ATC/AIMgr.cxx index 60519cc..8a13ae5 100644 --- a/src/ATC/AIMgr.cxx +++ b/src/ATC/AIMgr.cxx @@ -619,7 +619,7 @@ string FGAIMgr::GenerateShortForm(const string& callsign, const string& plane_st //cout << c << '\n'; string tmp = ""; tmp += c; - if(isalpha(c)) s += GetPhoneticIdent(c); + if(isalpha(c)) s += GetPhoneticLetter(c); else s += ConvertNumToSpokenDigits(tmp); if(i > 1) s += '-'; } diff --git a/src/ATC/AIMgr.hxx b/src/ATC/AIMgr.hxx index 6bc81a1..976def7 100644 --- a/src/ATC/AIMgr.hxx +++ b/src/ATC/AIMgr.hxx @@ -52,7 +52,7 @@ private: ai_list_iterator ai_list_itr; // Any member function of FGATCMgr is permitted to leave this iterator pointing // at any point in or at the end of the list. - // Hence any new access must explicitly first check for atc_list.end() before dereferencing. + // Hence any new access must explicitly first check for ai_list.end() before dereferencing. // A list of airport or airplane ID's typedef list < string > ID_list_type; @@ -114,9 +114,6 @@ private: bool initDone; // Hack - guard against update getting called before init - // Remove a class from the ai_list and delete it from memory - //void RemoveFromList(const char* id, atc_type tp); - // Activate AI traffic at an airport void ActivateAirport(const string& ident); diff --git a/src/ATC/AIPlane.cxx b/src/ATC/AIPlane.cxx index 8400629..aae4fdb 100644 --- a/src/ATC/AIPlane.cxx +++ b/src/ATC/AIPlane.cxx @@ -106,7 +106,9 @@ void FGAIPlane::Update(double dt) { if(1) { // For now assume in range !!! // TODO - implement range checking - Render(plane.callsign, false); + // TODO - at the moment the volume is always set off comm1 + double volume = fgGetDouble("/instrumentation/comm[0]/volume"); + Render(plane.callsign, volume, false); } } // Run the callback regardless of whether on same freq as user or not. @@ -166,7 +168,9 @@ void FGAIPlane::ConditionalTransmit(double timeout, int callback_code) { } void FGAIPlane::ImmediateTransmit(int callback_code) { - Render(plane.callsign, false); + // TODO - at the moment the volume is always set off comm1 + double volume = fgGetDouble("/instrumentation/comm[0]/volume"); + Render(plane.callsign, volume, false); if(callback_code) { ProcessCallback(callback_code); } @@ -180,26 +184,26 @@ void FGAIPlane::ProcessCallback(int code) { // Outputs the transmission either on screen or as audio depending on user preference // The refname is a string to identify this sample to the sound manager // The repeating flag indicates whether the message should be repeated continuously or played once. -void FGAIPlane::Render(const string& refname, bool repeating) { +void FGAIPlane::Render(const string& refname, const double volume, bool repeating) { fgSetString("/sim/messages/ai-plane", pending_transmission.c_str()); #ifdef ENABLE_AUDIO_SUPPORT voice = (voiceOK && fgGetBool("/sim/sound/voice")); if(voice) { - int len; - unsigned char* buf = vPtr->WriteMessage((char*)pending_transmission.c_str(), len, voice); - if(voice) { - SGSoundSample* simple = new SGSoundSample(buf, len, 8000); - // TODO - at the moment the volume is always set off comm1 - // and can't be changed after the transmission has started. - simple->set_volume(5.0 * fgGetDouble("/instrumentation/comm[0]/volume")); - globals->get_soundmgr()->add(simple, refname); - if(repeating) { - globals->get_soundmgr()->play_looped(refname); - } else { - globals->get_soundmgr()->play_once(refname); - } + int len; + string buf = vPtr->WriteMessage((char*)pending_transmission.c_str(), voice); + if(voice) { + SGSoundSample* simple = + new SGSoundSample((unsigned char*)buf.c_str(), buf.length(), 8000); + // TODO - at the moment the volume can't be changed + // after the transmission has started. + simple->set_volume(volume); + globals->get_soundmgr()->add(simple, refname); + if(repeating) { + globals->get_soundmgr()->play_looped(refname); + } else { + globals->get_soundmgr()->play_once(refname); } - delete[] buf; + } } #endif // ENABLE_AUDIO_SUPPORT if(!voice) { diff --git a/src/ATC/AIPlane.hxx b/src/ATC/AIPlane.hxx index 3cf218d..02ab50c 100644 --- a/src/ATC/AIPlane.hxx +++ b/src/ATC/AIPlane.hxx @@ -143,7 +143,7 @@ private: // Outputs the transmission either on screen or as audio depending on user preference // The refname is a string to identify this sample to the sound manager // The repeating flag indicates whether the message should be repeated continuously or played once. - void Render(const string& refname, bool repeating); + void Render(const string& refname, const double volume, bool repeating); // Cease rendering a transmission. // Requires the sound manager refname if audio, else "". diff --git a/src/ATC/ATC.cxx b/src/ATC/ATC.cxx index 2bc3218..244131f 100644 --- a/src/ATC/ATC.cxx +++ b/src/ATC/ATC.cxx @@ -30,35 +30,34 @@ #include "ATC.hxx" -FGATC::FGATC() { - freqClear = true; - receiving = false; - respond = false; - runResponseCounter = false; - _runReleaseCounter = false; - responseID = ""; - responseReqd = false; - _type = INVALID; - _display = false; - _displaying = false; - +FGATC::FGATC() : + freqClear(true), + receiving(false), + respond(false), + responseID(""), + runResponseCounter(false), + _runReleaseCounter(false), + responseReqd(false), + _type(INVALID), + _display(false), // Transmission timing stuff - pending_transmission = ""; - _timeout = 0; - _pending = false; - _callback_code = 0; - _transmit = false; - _transmitting = false; - _counter = 0.0; - _max_count = 5.0; - - _voiceOK = false; + pending_transmission(""), + _timeout(0), + _pending(false), + _callback_code(0), + _transmit(false), + _transmitting(false), + _counter(0.0), + _max_count(5.0), + _voiceOK(false) +{ } FGATC::~FGATC() { } -// Derived classes wishing to use the response counter should call this from their own Update(...). +// Derived classes wishing to use the response counter should +// call this from their own Update(...). void FGATC::Update(double dt) { if(runResponseCounter) { //cout << responseCounter << '\t' << responseTime << '\n'; @@ -216,7 +215,8 @@ void FGATC::SetData(ATCData* d) { // Outputs the transmission either on screen or as audio depending on user preference // The refname is a string to identify this sample to the sound manager // The repeating flag indicates whether the message should be repeated continuously or played once. -void FGATC::Render(string& msg, const string& refname, bool repeating) { +void FGATC::Render(string& msg, const double volume, + const string& refname, const bool repeating) { if (repeating) fgSetString("/sim/messages/atis", msg.c_str()); else @@ -225,26 +225,28 @@ void FGATC::Render(string& msg, const string& refname, bool repeating) { #ifdef ENABLE_AUDIO_SUPPORT _voice = (_voiceOK && fgGetBool("/sim/sound/voice")); if(_voice) { - int len; - unsigned char* buf = _vPtr->WriteMessage((char*)msg.c_str(), len, _voice); - if(_voice) { - try { - SGSoundSample *simple - = new SGSoundSample(buf, len, 8000); - // TODO - at the moment the volume is always set off comm1 - // and can't be changed after the transmission has started. - simple->set_volume(5.0 * fgGetDouble("/instrumentation/comm[0]/volume")); - globals->get_soundmgr()->add(simple, refname); - if(repeating) { - globals->get_soundmgr()->play_looped(refname); - } else { - globals->get_soundmgr()->play_once(refname); - } - } catch ( sg_io_exception &e ) { - SG_LOG(SG_GENERAL, SG_ALERT, e.getFormattedMessage()); - } + string buf = _vPtr->WriteMessage((char*)msg.c_str(), _voice); + if(_voice) { + NoRender(refname); + try { +// >>> Beware: must pass a (new) object to the (add) method, +// >>> because the (remove) method is going to do a (delete) +// >>> whether that's what you want or not. + SGSoundSample *simple = + new SGSoundSample((unsigned char*) buf.c_str(), buf.length(), 8000); + // TODO - at the moment the volume can't be changed + // after the transmission has started. + simple->set_volume(volume); + globals->get_soundmgr()->add(simple, refname); + if(repeating) { + globals->get_soundmgr()->play_looped(refname); + } else { + globals->get_soundmgr()->play_once(refname); + } + } catch ( sg_io_exception &e ) { + SG_LOG(SG_GENERAL, SG_ALERT, e.getFormattedMessage()); } - delete[] buf; + } } #endif // ENABLE_AUDIO_SUPPORT if(!_voice) { diff --git a/src/ATC/ATC.hxx b/src/ATC/ATC.hxx index 0ea9d9c..d8ccddb 100644 --- a/src/ATC/ATC.hxx +++ b/src/ATC/ATC.hxx @@ -102,12 +102,13 @@ struct RunwayDetails { ostream& operator << (ostream& os, atc_type atc); class FGATC { - +friend class FGATCMgr; public: FGATC(); virtual ~FGATC(); - + virtual void Init()=0; + // Run the internal calculations // Derived classes should call this method from their own Update methods if they // wish to use the response timer functionality. @@ -180,7 +181,8 @@ protected: // Outputs the transmission either on screen or as audio depending on user preference // The refname is a string to identify this sample to the sound manager // The repeating flag indicates whether the message should be repeated continuously or played once. - void Render(string& msg, const string& refname = "", bool repeating = false); + void Render(string& msg, const double volume = 1, + const string& refname = "", const bool repeating = false); // Cease rendering all transmission from this station. // Requires the sound manager refname if audio, else "". @@ -200,6 +202,8 @@ protected: double lon, lat, elev; double x, y, z; int freq; + map active_on; + int range; string ident; // Code of the airport its at. string name; // Name transmitted in the broadcast. @@ -226,10 +230,10 @@ protected: double _releaseTime; double _releaseCounter; - bool _display; // Flag to indicate whether we should be outputting to the ATC display. - bool _displaying; // Flag to indicate whether we are outputting to the ATC display. + bool _display; // Indicates we should be outputting to the ATC display. private: + // Transmission timing stuff. bool _pending; double _timeout; diff --git a/src/ATC/ATCVoice.cxx b/src/ATC/ATCVoice.cxx index 682cd24..0d27ad3 100644 --- a/src/ATC/ATCVoice.cxx +++ b/src/ATC/ATCVoice.cxx @@ -57,10 +57,14 @@ bool FGATCVoice::LoadVoice(const string& voice) { string file = voice + ".wav"; - SoundData = new SGSoundSample(); - rawSoundData = (char *)SoundData->load_file(path.c_str(), file.c_str()); - rawDataSize = SoundData->get_size(); - + SGSoundSample SoundData; + rawSoundData = (char *)SoundData.load_file(path.c_str(), file.c_str()); + rawDataSize = SoundData.get_size(); + ALenum fmt = SoundData.get_format(); +#ifdef VOICE_TEST + cout << "ATCVoice: format: " << fmt + << " size: " << rawDataSize << endl; +#endif path = globals->get_fg_root(); string wordPath = "ATC/" + voice + ".vce"; path.append(wordPath); @@ -105,8 +109,9 @@ bool FGATCVoice::LoadVoice(const string& voice) { typedef list < string > tokenList_type; typedef tokenList_type::iterator tokenList_iterator; -// Given a desired message, return a pointer to the data buffer and write the buffer length into len. -unsigned char* FGATCVoice::WriteMessage(char* message, int& len, bool& dataOK) { +// Given a desired message, return a string containing the +// sound-sample data +string FGATCVoice::WriteMessage(const char* message, bool& dataOK) { // What should we do here? // First - parse the message into a list of tokens. @@ -117,20 +122,22 @@ unsigned char* FGATCVoice::WriteMessage(char* message, int& len, bool& dataOK) { // TODO - at the moment we're effectively taking 3 passes through the data. // There is no need for this - 2 should be sufficient - we can probably ditch the tokenList. + size_t n1 = 1+strlen(message); + char msg[n1]; + strncpy(msg, message, n1); // strtok requires a non-const char* char* token; - char mes[1000]; int numWords = 0; - strcpy(mes, message); - const char delimiters[] = " \t.,;:\""; - token = strtok(mes, delimiters); + const char delimiters[] = " \t.,;:\"\n"; + char* context; + token = strtok_r(msg, delimiters, &context); while(token != NULL) { tokenList.push_back(token); ++numWords; //cout << "token = " << token << '\n'; - token = strtok(NULL, delimiters); + token = strtok_r(NULL, delimiters, &context); } - WordData* wdptr = new WordData[numWords]; + WordData wdptr[numWords]; int word = 0; unsigned int cumLength = 0; @@ -138,7 +145,7 @@ unsigned char* FGATCVoice::WriteMessage(char* message, int& len, bool& dataOK) { while(tokenListItr != tokenList.end()) { if(wordMap.find(*tokenListItr) == wordMap.end()) { // Oh dear - the token isn't in the sound file - //cout << "word " << *tokenListItr << " not found :-(\n"; + //cout << "word " << *tokenListItr << " not found.\n"; } else { wdptr[word] = wordMap[*tokenListItr]; cumLength += wdptr[word].length; @@ -151,12 +158,10 @@ unsigned char* FGATCVoice::WriteMessage(char* message, int& len, bool& dataOK) { // Check for no tokens found else slScheduler can be crashed if(!word) { dataOK = false; - return(NULL); + return ""; } - unsigned char* tmpbuf = new unsigned char[cumLength]; - unsigned char* outbuf = new unsigned char[cumLength]; - len = cumLength; + char tmpbuf[cumLength]; unsigned int bufpos = 0; for(int i=0; i cumLength) offsetIn = cumLength; - memcpy(outbuf, tmpbuf + offsetIn, (cumLength - offsetIn)); - memcpy(outbuf + (cumLength - offsetIn), tmpbuf, offsetIn); - - delete[] tmpbuf; - delete[] wdptr; + + string front(tmpbuf, offsetIn); + string back(tmpbuf+offsetIn, cumLength - offsetIn); dataOK = true; - return(outbuf); + return back + front; } diff --git a/src/ATC/ATCVoice.hxx b/src/ATC/ATCVoice.hxx index a5ea593..25672de 100644 --- a/src/ATC/ATCVoice.hxx +++ b/src/ATC/ATCVoice.hxx @@ -69,8 +69,7 @@ public: // Given a desired message, return a pointer to the data buffer and write the buffer length into len. // Sets dataOK = true if the returned buffer is valid. - unsigned char* WriteMessage(char* message, int& len, bool& dataOK); - + std::string WriteMessage(const char* message, bool& dataOK); private: diff --git a/src/ATC/ATCmgr.cxx b/src/ATC/ATCmgr.cxx index bbf99df..39e950d 100644 --- a/src/ATC/ATCmgr.cxx +++ b/src/ATC/ATCmgr.cxx @@ -24,6 +24,7 @@ #include #include + #include #include "ATCmgr.hxx" @@ -56,27 +57,13 @@ AirportATC::AirportATC() : numAI(0) //airport_atc_map.clear(); { - for(int i=0; ibegin(); // Search for connected ATC stations once per 0.8 seconds or so // globals->get_event_mgr()->add( "fgATCSearch()", fgATCSearch, @@ -164,41 +149,43 @@ void FGATCMgr::update(double dt) { //Traverse the list of active stations. //Only update one class per update step to avoid the whole ATC system having to calculate between frames. //Eventually we should only update every so many steps. - //cout << "In FGATCMgr::update - atc_list.size = " << atc_list.size() << endl; - if(atc_list.size()) { - if(atc_list_itr == atc_list.end()) { - atc_list_itr = atc_list.begin(); + //cout << "In FGATCMgr::update - atc_list.size = " << atc_list->size() << endl; + if(atc_list->size()) { + if(atc_list_itr == atc_list->end()) { + atc_list_itr = atc_list->begin(); } //cout << "Updating " << (*atc_list_itr)->get_ident() << ' ' << (*atc_list_itr)->GetType() << '\n'; //cout << "Freq = " << (*atc_list_itr)->get_freq() << '\n'; - (*atc_list_itr)->Update(dt * atc_list.size()); + (*atc_list_itr).second->Update(dt * atc_list->size()); //cout << "Done ATC update..." << endl; ++atc_list_itr; } - /* - cout << "ATC_LIST: " << atc_list.size() << ' '; - for(atc_list_iterator it = atc_list.begin(); it != atc_list.end(); it++) { +#ifdef ATC_TEST + cout << "ATC_LIST: " << atc_list->size() << ' '; + for(atc_list_iterator it = atc_list->begin(); it != atc_list->end(); it++) { cout << (*it)->get_ident() << ' '; } cout << '\n'; - */ +#endif // Search the tuned frequencies every now and then - this should be done with the event scheduler static int i = 0; // Very ugly - but there should only ever be one instance of FGATCMgr. - /* + /*** Area search is defeated. Why? if(i == 7) { //cout << "About to AreaSearch()" << endl; AreaSearch(); } - */ + ***/ if(i == 15) { - //cout << "About to search(1)" << endl; - FreqSearch(1); + //cout << "About to search navcomm1" << endl; + FreqSearch("comm", 0); + FreqSearch("nav", 0); } if(i == 30) { - //cout << "About to search(2)" << endl; - FreqSearch(2); + //cout << "About to search navcomm2" << endl; + FreqSearch("comm", 1); + FreqSearch("nav", 1); i = 0; } ++i; @@ -255,7 +242,6 @@ bool FGATCMgr::AIRegisterAirport(const string& ident) { return(false); } - // Register the fact that the comm radio is tuned to an airport // Channel is zero based bool FGATCMgr::CommRegisterAirport(const string& ident, int chan, const atc_type& tp) { @@ -263,7 +249,7 @@ bool FGATCMgr::CommRegisterAirport(const string& ident, int chan, const atc_type //cout << "Comm channel " << chan << " registered airport " << ident << ' ' << tp << '\n'; if(airport_atc_map.find(ident) != airport_atc_map.end()) { //cout << "IN MAP - flagging set by comm..." << endl; - airport_atc_map[ident]->set_by_comm[chan][tp] = true; +//xx airport_atc_map[ident]->set_by_comm[chan][tp] = true; if(tp == ATIS) { airport_atc_map[ident]->atis_active = true; } else if(tp == TOWER) { @@ -301,7 +287,7 @@ bool FGATCMgr::CommRegisterAirport(const string& ident, int chan, const atc_type // TODO - some airports will have a tower/ground frequency but be inactive overnight. a->set_by_AI = false; a->numAI = 0; - a->set_by_comm[chan][tp] = true; +//xx a->set_by_comm[chan][tp] = true; airport_atc_map[ident] = a; return(true); } @@ -309,94 +295,32 @@ bool FGATCMgr::CommRegisterAirport(const string& ident, int chan, const atc_type return(false); } - -// Remove from list only if not needed by the AI system or the other comm channel -// Note that chan is zero based. -void FGATCMgr::CommRemoveFromList(const string& id, const atc_type& tp, int chan) { - SG_LOG(SG_ATC, SG_BULK, "CommRemoveFromList called for airport " << id << " " << tp << " by channel " << chan); - //cout << "CommRemoveFromList called for airport " << id << " " << tp << " by channel " << chan << '\n'; - if(airport_atc_map.find(id) != airport_atc_map.end()) { - AirportATC* a = airport_atc_map[id]; - //cout << "In CommRemoveFromList, a->ground_freq = " << a->ground_freq << endl; - if(a->set_by_AI && tp != ATIS) { - // Set by AI, so don't remove simply because user isn't tuned in any more - just stop displaying - SG_LOG(SG_ATC, SG_BULK, "In CommRemoveFromList, service was set by AI\n"); - FGATC* aptr = GetATCPointer(id, tp); - switch(chan) { - case 0: - //cout << "chan 1\n"; - a->set_by_comm[0][tp] = false; - if(!a->set_by_comm[1][tp]) { - //cout << "not set by comm2\n"; - if(aptr != NULL) { - //cout << "Got pointer\n"; - aptr->SetNoDisplay(); - //cout << "Setting no display...\n"; - } else { - //cout << "Not got pointer\n"; - } - } - break; - case 1: - a->set_by_comm[1][tp] = false; - if(!a->set_by_comm[0][tp]) { - if(aptr != NULL) { - aptr->SetNoDisplay(); - //cout << "Setting no display...\n"; - } - } - break; - } - //airport_atc_map[id] = a; - return; - } else { - switch(chan) { - case 0: - a->set_by_comm[0][tp] = false; - // Remove only if not also set by the other comm channel - if(!a->set_by_comm[1][tp]) { - a->tower_active = false; - a->ground_active = false; - RemoveFromList(id, tp); - } - break; - case 1: - a->set_by_comm[1][tp] = false; - if(!a->set_by_comm[0][tp]) { - a->tower_active = false; - a->ground_active = false; - RemoveFromList(id, tp); - } - break; - } - } - } -} - - -// Remove from list - should only be called from above or similar -// This function *will* remove it from the list regardless of who else might want it. -void FGATCMgr::RemoveFromList(const string& id, const atc_type& tp) { - //cout << "FGATCMgr::RemoveFromList called..." << endl; - //cout << "Requested type = " << tp << endl; - //cout << "id = " << id << endl; - atc_list_iterator it = atc_list.begin(); - while(it != atc_list.end()) { - //cout << "type = " << (*it)->GetType() << '\n'; - //cout << "Ident = " << (*it)->get_ident() << '\n'; - if( ((*it)->get_ident() == id) - && ((*it)->GetType() == tp) ) { - //Before removing it stop it transmitting!! - //cout << "OBLITERATING FROM LIST!!!\n"; - (*it)->SetNoDisplay(); - (*it)->Update(0.00833); - delete (*it); - atc_list.erase(it); - atc_list_itr = atc_list.begin(); // Reset the persistent itr incase we've left it off the end. - break; - } - ++it; - } +typedef map MSI; + +void FGATCMgr::ZapOtherService(const string ncunit, const string svc_name){ + for (atc_list_iterator svc = atc_list->begin(); svc != atc_list->end(); svc++) { + //cout << "Zapping " << navcomm + // << "[" << unit << "]" << " otherthan: " << svc_name << endl; + if (svc->first != svc_name) { + MSI &actv = svc->second->active_on; + // OK, we have found some OTHER service; + // see if it is (was) active on our unit: + if (!actv.count(ncunit)) continue; + // cout << "Eradicating '" << svc->first << "' from: " << ncunit << endl; + actv.erase(ncunit); + if (!actv.size()) { + //cout << "Eradicating service: '" << svc->first << "'" << endl; + svc->second->SetNoDisplay(); + svc->second->Update(0); // one last update + delete svc->second; + atc_list->erase(svc); +// ALL pointers into the ATC list are now invalid, +// so let's reset them: + atc_list_itr = atc_list->begin(); + } + break; // cannot be duplicates in the active list + } + } } @@ -404,18 +328,9 @@ void FGATCMgr::RemoveFromList(const string& id, const atc_type& tp) { // Return NULL if the given service is not in the list // - *** THE CALLING FUNCTION MUST CHECK FOR THIS *** FGATC* FGATCMgr::FindInList(const string& id, const atc_type& tp) { - //cout << "Entering FindInList for " << id << ' ' << tp << endl; - atc_list_iterator it = atc_list.begin(); - while(it != atc_list.end()) { - if( ((*it)->get_ident() == id) - && ((*it)->GetType() == tp) ) { - return(*it); - } - ++it; - } - // If we get here it's not in the list - //cout << "Couldn't find it in the list though :-(" << endl; - return(NULL); + string ndx = id + decimal(tp); + if (!atc_list->count(ndx)) return 0; + return (*atc_list)[ndx]; } // Returns true if the airport is found in the map @@ -453,7 +368,7 @@ FGATC* FGATCMgr::GetATCPointer(const string& icao, const atc_type& type) { if(current_commlist->FindByFreq(a->lon, a->lat, a->elev, a->tower_freq, &data, TOWER)) { FGTower* t = new FGTower; t->SetData(&data); - atc_list.push_back(t); + (*atc_list)[icao+decimal(type)] = t; a->tower_active = true; airport_atc_map[icao] = a; //cout << "Initing tower " << icao << " in GetATCPointer()\n"; @@ -479,7 +394,7 @@ FGATC* FGATCMgr::GetATCPointer(const string& icao, const atc_type& type) { if(current_commlist->FindByFreq(a->lon, a->lat, a->elev, a->ground_freq, &data, GROUND)) { FGGround* g = new FGGround; g->SetData(&data); - atc_list.push_back(g); + (*atc_list)[icao+decimal(type)] = g; a->ground_active = true; airport_atc_map[icao] = a; g->Init(); @@ -534,190 +449,125 @@ FGATCVoice* FGATCMgr::GetVoicePointer(const atc_type& type) { } // Search for ATC stations by frequency -void FGATCMgr::FreqSearch(int channel) { - int chan = channel - 1; // Convert to zero-based for the arrays +void FGATCMgr::FreqSearch(const string navcomm, const int unit) { + + + string ncunit = navcomm + "[" + decimal(unit) + "]"; + string commbase = "/instrumentation/" + ncunit; + string commfreq = commbase + "/frequencies/selected-mhz"; + SGPropertyNode_ptr comm_node = fgGetNode(commfreq.c_str(), false); + + //cout << "FreqSearch: " << ncunit + // << " node: " << comm_node << endl; + if (!comm_node) return; // no such radio unit ATCData data; - double freq = comm_node[chan]->getDoubleValue(); + double freq = comm_node->getDoubleValue(); lon = lon_node->getDoubleValue(); lat = lat_node->getDoubleValue(); elev = elev_node->getDoubleValue() * SG_FEET_TO_METER; // Query the data store and get the closest match if any + // cout << "FindByFreq: " << lat << " " << lon << " " << elev + // << " freq: " << freq << endl; if(current_commlist->FindByFreq(lon, lat, elev, freq, &data)) { - // We have a match - // What's the logic? - // If this channel not previously valid then easy - add ATC to list - // If this channel was valid then - Have we tuned to a different service? - // If so - de-register one and add the other - if(comm_valid[chan]) { - if((comm_ident[chan] == data.ident) && (comm_type[chan] == data.type)) { - // Then we're still tuned into the same service so do nought and return - return; - } else { - // Something's changed - either the location or the service type - // We need to feed the channel in so we're not removing it if we're also tuned in on the other channel - CommRemoveFromList(comm_ident[chan], comm_type[chan], chan); - } - } - // At this point we can assume that we need to add the service. - comm_ident[chan] = data.ident; - comm_type[chan] = data.type; - comm_x[chan] = (double)data.x; - comm_y[chan] = (double)data.y; - comm_z[chan] = (double)data.z; - comm_lon[chan] = (double)data.lon; - comm_lat[chan] = (double)data.lat; - comm_elev[chan] = (double)data.elev; - comm_valid[chan] = true; - - // This was a switch-case statement but the compiler didn't like the new variable creation with it. - if(comm_type[chan] == ATIS) { - CommRegisterAirport(comm_ident[chan], chan, ATIS); - FGATC* app = FindInList(comm_ident[chan], ATIS); - if(app != NULL) { - // The station is already in the ATC list - //cout << "In list - flagging SetDisplay..." << endl; - comm_atc_ptr[chan] = app; - app->SetDisplay(); - } else { - // Generate the station and put in the ATC list - //cout << "Not in list - generating..." << endl; - FGATIS* a = new FGATIS; - a->SetData(&data); - comm_atc_ptr[chan] = a; - a->SetDisplay(); - //a->Init(); - atc_list.push_back(a); - } - } else if (comm_type[chan] == TOWER) { - //cout << "TOWER TOWER TOWER\n"; - CommRegisterAirport(comm_ident[chan], chan, TOWER); - //cout << "Done (TOWER)" << endl; - FGATC* app = FindInList(comm_ident[chan], TOWER); - if(app != NULL) { - // The station is already in the ATC list - SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is in list - flagging SetDisplay..."); - //cout << comm_ident[chan] << " is in list - flagging SetDisplay...\n"; - comm_atc_ptr[chan] = app; - app->SetDisplay(); - } else { - // Generate the station and put in the ATC list - SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is not in list - generating..."); - //cout << comm_ident[chan] << " is not in list - generating...\n"; - FGTower* t = new FGTower; - t->SetData(&data); - comm_atc_ptr[chan] = t; - //cout << "Initing tower in FreqSearch()\n"; - t->Init(); - t->SetDisplay(); - atc_list.push_back(t); - } - } else if (comm_type[chan] == GROUND) { - CommRegisterAirport(comm_ident[chan], chan, GROUND); - //cout << "Done (GROUND)" << endl; - FGATC* app = FindInList(comm_ident[chan], GROUND); - if(app != NULL) { - // The station is already in the ATC list - comm_atc_ptr[chan] = app; - app->SetDisplay(); - } else { - // Generate the station and put in the ATC list - FGGround* g = new FGGround; - g->SetData(&data); - comm_atc_ptr[chan] = g; - g->Init(); - g->SetDisplay(); - atc_list.push_back(g); - } - } else if (comm_type[chan] == APPROACH) { - // We have to be a bit more carefull here since approaches are also searched by area - CommRegisterAirport(comm_ident[chan], chan, APPROACH); - //cout << "Done (APPROACH)" << endl; - FGATC* app = FindInList(comm_ident[chan], APPROACH); - if(app != NULL) { - // The station is already in the ATC list - app->AddPlane("Player"); - app->SetDisplay(); - comm_atc_ptr[chan] = app; - } else { - // Generate the station and put in the ATC list - FGApproach* a = new FGApproach; - a->SetData(&data); - comm_atc_ptr[chan] = a; - a->Init(); - a->SetDisplay(); - a->AddPlane("Player"); - atc_list.push_back(a); - } - } + // cout << "FoundByFreq: " << freq + // << " ident: " << data.ident + // << " type: " << data.type << " ***" << endl; + // We are in range of something. + + +// Get rid of any *other* service that was on this radio unit: + string svc_name = data.ident+decimal(data.type); + ZapOtherService(ncunit, svc_name); +// See if the service already exists, possibly connected to +// some other radio unit: + if (atc_list->count(svc_name)) { + // make sure the service knows it's tuned on this radio: + FGATC* svc = (*atc_list)[svc_name]; + svc->active_on[ncunit] = 1; + svc->SetDisplay(); + if (data.type == APPROACH) svc->AddPlane("Player"); + return; + } + + CommRegisterAirport(data.ident, unit, data.type); + +// This was a switch-case statement but the compiler didn't like +// the new variable creation with it. + if (data.type == ATIS) (*atc_list)[svc_name] = new FGATIS; + else if (data.type == TOWER) (*atc_list)[svc_name] = new FGTower; + else if (data.type == GROUND) (*atc_list)[svc_name] = new FGGround; + else if (data.type == APPROACH) (*atc_list)[svc_name] = new FGApproach; + FGATC* svc = (*atc_list)[svc_name]; + svc->SetData(&data); + svc->active_on[ncunit] = 1; + svc->SetDisplay(); + svc->Init(); + if (data.type == APPROACH) svc->AddPlane("Player"); } else { - if(comm_valid[chan]) { - if(comm_type[chan] != APPROACH) { - // Currently approaches are removed by Alexander's out-of-range mechanism - CommRemoveFromList(comm_ident[chan], comm_type[chan], chan); - } - // Note that we *don't* call SetNoDisplay() here because the other comm channel - // might be tuned into the same station - this is handled by CommRemoveFromList(...) - comm_type[chan] = INVALID; - comm_atc_ptr[chan] = NULL; - comm_valid[chan] = false; - } - } + // No services in range. Zap any service on this unit. + ZapOtherService(ncunit, "x x x"); + } } +#ifdef AREA_SEARCH +/* I don't think AreaSearch ever gets called */ // Search ATC stations by area in order that we appear 'on the radar' void FGATCMgr::AreaSearch() { - // Search for Approach stations - comm_list_type approaches; - comm_list_iterator app_itr; - - lon = lon_node->getDoubleValue(); - lat = lat_node->getDoubleValue(); - elev = elev_node->getDoubleValue() * SG_FEET_TO_METER; - - // search stations in range - int num_app = current_commlist->FindByPos(lon, lat, elev, 100.0, &approaches, APPROACH); - if (num_app != 0) { - //cout << num_app << " approaches found in radiostack search !!!!" << endl; - - for(app_itr = approaches.begin(); app_itr != approaches.end(); app_itr++) { - - FGATC* app = FindInList(app_itr->ident, app_itr->type); - if(app != NULL) { - // The station is already in the ATC list - //cout << "In list adding player\n"; - app->AddPlane("Player"); - //app->Update(); - } else { - // Generate the station and put in the ATC list - FGApproach* a = new FGApproach; - a->SetData(&(*app_itr)); - //cout << "Adding player\n"; - a->AddPlane("Player"); - //a->Update(); - atc_list.push_back(a); - } - } - } - - // remove planes which are out of range - // TODO - I'm not entirely sure that this belongs here. - atc_list_iterator it = atc_list.begin(); - while(it != atc_list.end()) { - if((*it)->GetType() == APPROACH ) { - int np = (*it)->RemovePlane(); - // if approach has no planes left remove it from ATC list - if ( np == 0) { - //cout << "REMOVING AN APPROACH STATION WITH NO PLANES..." << endl; - (*it)->SetNoDisplay(); - (*it)->Update(0.00833); - delete (*it); - atc_list.erase(it); - atc_list_itr = atc_list.begin(); // Reset the persistent itr incase we've left it off the end. - break; // the other stations will be checked next time - } - } - ++it; - } + const string AREA("AREA"); + // Search for Approach stations + comm_list_type approaches; + comm_list_iterator app_itr; + + lon = lon_node->getDoubleValue(); + lat = lat_node->getDoubleValue(); + elev = elev_node->getDoubleValue() * SG_FEET_TO_METER; + for (atc_list_iterator svc = atc_list->begin(); svc != atc_list->end(); svc++) { + MSI &actv = svc->second->active_on; + if (actv.count(AREA)) actv[AREA] = 0; // Mark all as maybe not in range + } + + // search stations in range + int num_app = current_commlist->FindByPos(lon, lat, elev, 100.0, &approaches, APPROACH); + if (num_app != 0) { + //cout << num_app << " approaches found in area search !!!!" << endl; + + for(app_itr = approaches.begin(); app_itr != approaches.end(); app_itr++) { + FGATC* app = FindInList(app_itr->ident, app_itr->type); + string svc_name = app_itr->ident+decimal(app_itr->type); + if(app != NULL) { + // The station is already in the ATC list + app->AddPlane("Player"); + } else { + // Generate the station and put in the ATC list + FGApproach* a = new FGApproach; + a->SetData(&(*app_itr)); + a->AddPlane("Player"); + (*atc_list)[svc_name] = a; + //cout << "New area service: " << svc_name << endl; + } + FGATC* svc = (*atc_list)[svc_name]; + svc->active_on[AREA] = 1; + } + } + + for (atc_list_iterator svc = atc_list->begin(); svc != atc_list->end(); svc++) { + MSI &actv = svc->second->active_on; + if (!actv.count(AREA)) continue; + if (!actv[AREA]) actv.erase(AREA); + if (!actv.size()) { // this service no longer active at all + cout << "Eradicating area service: " << svc->first << endl; + svc->second->SetNoDisplay(); + svc->second->Update(0); + delete (svc->second); + atc_list->erase(svc); +// Reset the persistent iterator, since any erase() makes it invalid: + atc_list_itr = atc_list->begin(); +// Hope we only move out of one approach-area; +// others will not be noticed until next update: + break; + } + } } +#endif diff --git a/src/ATC/ATCmgr.hxx b/src/ATC/ATCmgr.hxx index 9e8e1f8..cedb52a 100644 --- a/src/ATC/ATCmgr.hxx +++ b/src/ATC/ATCmgr.hxx @@ -65,7 +65,7 @@ struct AirportATC { // Flags to ensure the stations don't get wrongly deactivated bool set_by_AI; // true when the AI manager has activated this station unsigned int numAI; // Ref count of the number of AI planes registered - bool set_by_comm[2][ATC_NUM_TYPES]; // true when the relevant comm_freq has activated this station and type +//xx bool set_by_comm[2][ATC_NUM_TYPES]; // true when the relevant comm_freq has activated this station and type }; class FGATCMgr : public SGSubsystem @@ -84,13 +84,13 @@ private: airport_atc_map_iterator airport_atc_map_itr; // A list of pointers to all currently active ATC classes - typedef list atc_list_type; + typedef map atc_list_type; typedef atc_list_type::iterator atc_list_iterator; typedef atc_list_type::const_iterator atc_list_const_iterator; // Everything put in this list should be created dynamically // on the heap and ***DELETED WHEN REMOVED!!!!!*** - atc_list_type atc_list; + atc_list_type* atc_list; atc_list_iterator atc_list_itr; // Any member function of FGATCMgr is permitted to leave this iterator pointing // at any point in or at the end of the list. @@ -101,30 +101,12 @@ private: double lat; double elev; - // Type of ATC control that the user's radios are tuned to. - atc_type comm_type[2]; - - // Pointer to the ATC station that the user is currently tuned into. - FGATC* comm_atc_ptr[2]; - - double comm_freq[2]; - - // Pointers to users current communication frequencies. - SGPropertyNode_ptr comm_node[2]; // Pointers to current users position SGPropertyNode_ptr lon_node; SGPropertyNode_ptr lat_node; SGPropertyNode_ptr elev_node; - // Position of the ATC that the comm radios are tuned to in order to decide - // whether transmission will be received. - double comm_x[2], comm_y[2], comm_z[2], comm_lon[2], comm_lat[2], comm_elev[2]; - - double comm_range[2], comm_effective_range[2]; - bool comm_valid[2]; - string comm_ident[2]; - //string last_comm_ident[2]; //string approach_ident; bool last_in_range; @@ -170,10 +152,10 @@ public: // at different airports in quick succession if a large enough selection are available. FGATCVoice* GetVoicePointer(const atc_type& type); - atc_type GetComm1ATCType() { return(comm_type[0]); } - FGATC* GetComm1ATCPointer() { return(comm_atc_ptr[0]); } - atc_type GetComm2ATCType() { return(comm_type[1]); } - FGATC* GetComm2ATCPointer() { return(comm_atc_ptr[1]); } + atc_type GetComm1ATCType() { return(INVALID/* kludge */); } + FGATC* GetComm1ATCPointer() { return(0/* kludge */); } + atc_type GetComm2ATCType() { return(INVALID); } + FGATC* GetComm2ATCPointer() { return(0/* kludge */); } // Get the frequency of a given service at a given airport // Returns zero if not found @@ -189,11 +171,7 @@ private: // Remove a class from the atc_list and delete it from memory // *if* no other comm channel or AI plane is using it. - void CommRemoveFromList(const string& id, const atc_type& tp, int chan); - - // Remove a class from the atc_list and delete it from memory - // Should be called from the above - not directly!! - void RemoveFromList(const string& id, const atc_type& tp); + void ZapOtherService(const string ncunit, const string svc_name); // Return a pointer to a class in the list given ICAO code and type // (external interface to this is through GetATCPointer) @@ -201,11 +179,13 @@ private: // - *** THE CALLING FUNCTION MUST CHECK FOR THIS *** FGATC* FindInList(const string& id, const atc_type& tp); - // Search the specified channel for stations on the same frequency and in range. - void FreqSearch(int channel); + // Search the specified radio for stations on the same frequency and in range. + void FreqSearch(const string navcomm, const int unit); - // Search ATC stations by area in order that we appear 'on the radar' - void AreaSearch(); +#ifdef AREA_SEARCH + // Search ATC stations by area in order that we appear 'on the radar' + void AreaSearch(); +#endif }; diff --git a/src/ATC/ATCutils.cxx b/src/ATC/ATCutils.cxx index 2f33117..653639d 100644 --- a/src/ATC/ATCutils.cxx +++ b/src/ATC/ATCutils.cxx @@ -38,7 +38,16 @@ #include "ATCutils.hxx" #include "ATCProjection.hxx" -static const string nums[10] = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "niner"}; +static const string nums[10] = {"zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "niner"}; + +static const string letters[LTRS] = { + "alpha", "bravo", "charlie", "delta", "echo", + "foxtrot", "golf", "hotel", "india", "juliet", + "kilo", "lima", "mike", "november", "oscar", + "papa", "quebec", "romeo", "sierra", "tango", + "uniform", "victor", "whiskey", "xray", "yankee", "zulu" +}; // Convert any number to spoken digits string ConvertNumToSpokenDigits(const string &n) { @@ -75,92 +84,37 @@ string ConvertNumToSpokenDigits(const int& n) { } -// Convert a 2 digit rwy number to a spoken-style string -string ConvertRwyNumToSpokenString(int n) { - // Basic error/sanity checking - while(n < 0) { - n += 36; - } - while(n > 36) { - n -= 36; - } - if(n == 0) { - n = 36; // Is this right? - } - - string str = ""; - int index = n/10; - str += nums[index]; - n -= (index * 10); - //str += "-"; - str += " "; //Changed this for the benefit of the voice token parser - prefer the "-" in the visual output though. - str += nums[n]; - return(str); -} - -// Assumes we get a two-digit string optionally appended with L, R or C -// eg 01 07L 29R 36 +// Assumes we get a string of digits optionally appended with L, R or C +// eg 1 7L 29R 36 // Anything else is not guaranteed to be handled correctly! -string ConvertRwyNumToSpokenString(const string &s) { - if(s.size() < 3) { - return(ConvertRwyNumToSpokenString(atoi(s.c_str()))); - } else { - string r = ConvertRwyNumToSpokenString(atoi(s.substr(0,2).c_str())); - if(s.substr(2,1) == "L") { - r += " left"; - } else if(s.substr(2,1) == "R") { - r += " right"; - } else if(s.substr(2,1) == "C") { - r += " center"; - } else { - SG_LOG(SG_ATC, SG_WARN, "WARNING: Unknown suffix " << s.substr(2,1) << " from runway ID " << s << " in ConvertRwyNumToSpokenString(...)"); - } - return(r); - } +string ConvertRwyNumToSpokenString(const string &rwy) { + string rslt; + for (int ii = 0; ii < rwy.length(); ii++){ + if (rslt.length()) rslt += " "; + string ch = rwy.substr(ii,1); + if (isdigit(ch[0])) rslt += ConvertNumToSpokenDigits(atoi(ch.c_str())); + else if (ch == "R") rslt += "right"; + else if (ch == "C") rslt += "center"; + else if (ch == "L") rslt += "left"; + else { + rslt += GetPhoneticLetter(ch[0]); + SG_LOG(SG_ATC, SG_WARN, "WARNING: Unknown suffix '" << ch + << "' in runway " << rwy << " in ConvertRwyNumToSpokenString(...)"); + } + } + return rslt; } // Return the phonetic letter of a letter represented as an integer 1->26 -string GetPhoneticIdent(int i) { - // TODO - Check i is between 1 and 26 and wrap if necessary - return(GetPhoneticIdent(char('a' + (i-1)))); +string GetPhoneticLetter(const int i) { + return(letters[i % LTRS]); } // Return the phonetic letter of a character in the range a-z or A-Z. // Currently always returns prefixed by lowercase. -string GetPhoneticIdent(char c) { - c = tolower(c); - // TODO - Check c is between a and z and wrap if necessary - switch(c) { - case 'a' : return("alpha"); - case 'b' : return("bravo"); - case 'c' : return("charlie"); - case 'd' : return("delta"); - case 'e' : return("echo"); - case 'f' : return("foxtrot"); - case 'g' : return("golf"); - case 'h' : return("hotel"); - case 'i' : return("india"); - case 'j' : return("juliet"); - case 'k' : return("kilo"); - case 'l' : return("lima"); - case 'm' : return("mike"); - case 'n' : return("november"); - case 'o' : return("oscar"); - case 'p' : return("papa"); - case 'q' : return("quebec"); - case 'r' : return("romeo"); - case 's' : return("sierra"); - case 't' : return("tango"); - case 'u' : return("uniform"); - case 'v' : return("victor"); - case 'w' : return("whiskey"); - case 'x' : return("x-ray"); - case 'y' : return("yankee"); - case 'z' : return("zulu"); - } - // We shouldn't get here - return("Error"); +string GetPhoneticLetter(const char c) { + return GetPhoneticLetter(int(tolower(c) - 'a')); } // Get the compass direction associated with a heading in degrees diff --git a/src/ATC/ATCutils.hxx b/src/ATC/ATCutils.hxx index 04a7cad..432e6d8 100644 --- a/src/ATC/ATCutils.hxx +++ b/src/ATC/ATCutils.hxx @@ -44,22 +44,19 @@ string ConvertNumToSpokenDigits(const string &n); string ConvertNumToSpokenDigits(const int& n); string decimal(const int& n); -// Convert a 2 digit rwy number to a spoken-style string -string ConvertRwyNumToSpokenString(int n); - // Convert rwy number string to a spoken-style string -// eg "05L" to "zero five left" -// Assumes we get a two-digit string optionally appended with R, L, or C -// eg 01 07L 29R 36 -// Anything else is not guaranteed to be handled correctly! +// eg "15L" to "one five left" +// Assumes we get a string of digits optionally appended with R, L, or C +// eg 1 7L 29R 36 string ConvertRwyNumToSpokenString(const string &s); -// Return the phonetic letter of a letter represented as an integer 1->26 -string GetPhoneticIdent(int i); +const int LTRS(26); +// Return the phonetic letter of a letter represented as an integer 0..25 +string GetPhoneticLetter(const int i); // Return the phonetic letter of a character in the range a-z or A-Z. // Currently always returns prefixed by lowercase. -string GetPhoneticIdent(char c); +string GetPhoneticLetter(char c); // Get the compass direction associated with a heading in degrees // Currently returns 8 direction resolution (N, NE, E etc...) diff --git a/src/ATC/atis.cxx b/src/ATC/atis.cxx index 0957d18..59ea5a3 100644 --- a/src/ATC/atis.cxx +++ b/src/ATC/atis.cxx @@ -19,6 +19,13 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +///// +///// TODO: _Cumulative_ sky coverage. +///// TODO: wind _gust_ +///// TODO: more-sensible encoding of voice samples +///// u-law? outright synthesis? +///// TODO: AWOS and ASOS implementation. +///// #ifdef HAVE_CONFIG_H # include @@ -38,6 +45,7 @@ SG_USING_STD(cout); #include #include +#include #include
#include
@@ -49,177 +57,308 @@ SG_USING_STD(cout); #include "ATCmgr.hxx" FGATIS::FGATIS() : - transmission(""), - trans_ident(""), - atis_failed(false), - refname("atis") - //type(ATIS) + transmission(""), + trans_ident(""), + atis_failed(false), + refname("atis"), + _prev_display(0), + old_volume(0), + attention(0) { - _vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS); - _voiceOK = (_vPtr == NULL ? false : true); - _type = ATIS; + _vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS); + _voiceOK = (_vPtr == NULL ? false : true); + _type = ATIS; + fgTie("/environment/attention", this, (int_getter)0, &FGATIS::attend); } +// Hint: +// http://localhost:5400/environment/attention?value=1&submit=update + FGATIS::~FGATIS() { + fgUntie("/environment/attention"); +} + +void FGATIS::Init() { +// Nothing to see here. Move along. } +void +FGATIS::attend (int attn) +{ + attention = attn; +} + + // Main update function - checks whether we are displaying or not the correct message. void FGATIS::Update(double dt) { - if(_display) { - if(_displaying) { - // Check if we need to update the message - // - basically every hour and if the weather changes significantly at the station - } else { - // We need to get and display the message - UpdateTransmission(); - //cout << "ATIS.CXX - calling ATCMgr to render transmission..." << endl; - Render(transmission, refname, true); - _displaying = true; - } - } else { - // We shouldn't be displaying - if(_displaying) { - //cout << "ATIS.CXX - calling NoRender()..." << endl; - NoRender(refname); - _displaying = false; - } - } +#if 0 +Called from: +:#1 0x080ca378 in FGATIS::Update (this=0xa66d2a38, dt=0.050000000000000003) at atis.cxx:77 +:#2 0x080b174e in FGATCMgr::update (this=0xb38f130, dt=0.025000000000000001) at ATCmgr.cxx:175 +:#3 0x0805e92a in fgMainLoop () at main.cxx:458 +:#4 0x0809bce1 in GLUTidle () at fg_os.cxx:122 +:#5 0xb7b06d3a in glutMainLoop () from /usr/lib/libglut.so.3 +:#6 0x0805bbad in fgMainInit (argc=1, argv=0xbffce9e4) at main.cxx:1029 +#endif + cur_time = globals->get_time_params()->get_cur_time(); + msg_OK = (msg_time < cur_time); +#if 0 + if (msg_OK || _display != _prev_display) { + cout << "ATIS Update: " << _display << " " << _prev_display + << " len: " << transmission.length() + << " oldvol: " << old_volume + << " dt: " << dt << endl; + msg_time = cur_time; + } +#endif + if(_display) { + double volume(0); + for (map::iterator act = active_on.begin(); + act != active_on.end(); act++) { + string prop = "/instrumentation/" + act->first + "/volume"; + volume += globals->get_props()->getDoubleValue(prop.c_str()); + } + +// 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 rslt = GenTransmission(!_prev_display, attention); + if (rslt || volume != old_volume) { + //cout << "ATIS calling ATC::render volume: " << volume << endl; + Render(transmission, volume, refname, true); + old_volume = volume; + } + } else { +// We shouldn't be displaying + //cout << "ATIS.CXX - calling NoRender()..." << endl; + NoRender(refname); + } + _prev_display = _display; + attention = 0; } -// Sets the actual broadcast ATIS transmission. -void FGATIS::UpdateTransmission() { - double visibility; - char buf[10]; - int phonetic_id; - string phonetic_id_string; - string time_str = fgGetString("sim/time/gmt-string"); - int hours; - // int minutes; - - FGEnvironment stationweather = - ((FGEnvironmentMgr *)globals->get_subsystem("environment")) - ->getEnvironment(lat, lon, 0.0); - - transmission = ""; - - // UK CAA radiotelephony manual indicated ATIS transmissions start with "This is" - // Not sure about rest of the world though. - transmission += "This_is "; - // transmitted station name. - transmission += name; - // Add "Information" - transmission += " information"; - - //cout << "In atis.cxx, time_str = " << time_str << '\n'; - // Add the recording identifier - // For now we will assume we only transmit every hour - hours = atoi((time_str.substr(1,2)).c_str()); //Warning - this is fragile if the - //time string format changes - //cout << "In atis.cxx, hours = " << hours << endl; - phonetic_id = current_commlist->GetCallSign(ident, hours, 0); - phonetic_id_string = GetPhoneticIdent(phonetic_id); - transmission += " "; - transmission += phonetic_id_string; - - // Output the recording time. - we'll just output the last whole hour for now. - // FIXME - this only gets GMT time but that appears to be all the clock outputs for now - //cout << "in atis.cxx, time = " << time_str << endl; - transmission = transmission + " / Weather " + ConvertNumToSpokenDigits((time_str.substr(0,3) + "00")) + " hours zulu"; - - // Get the temperature - int temp; - temp = (int)stationweather.get_temperature_degc(); - - // HACK ALERT - at the moment the new environment subsystem returns bogus temperatures - // FIXME - take out this hack when this gets fixed upstream - if((temp < -50) || (temp > 60)) { - temp = 15; - } - - sprintf(buf, "%i", abs(temp)); - transmission += " / Temperature "; - if(temp < 0) { - transmission += "minus "; - } - string tempstr1 = buf; - string tempstr2; - transmission += ConvertNumToSpokenDigits(tempstr1); - transmission += " degrees_Celsius"; - - // Get the visibility - visibility = stationweather.get_visibility_m(); - sprintf(buf, "%i", int(visibility/1600)); - transmission += " / Visibility "; - tempstr1 = buf; - transmission += ConvertNumToSpokenDigits(tempstr1); - transmission += " miles"; - - // Get the cloudbase - // FIXME: kludge for now - if (strcmp(fgGetString("/environment/clouds/layer[0]/type"), "clear")) { - double cloudbase = - fgGetDouble("/environment/clouds/layer[0]/elevation-ft"); - // For some reason the altitude returned doesn't seem to correspond to the actual cloud altitude. - char buf3[10]; - char buf4[10]; - // cout << "cloudbase = " << cloudbase << endl; - sprintf(buf3, "%i", int(cloudbase)/1000); - sprintf(buf4, "%i", ((int)cloudbase % 1000)/100); - transmission += " / Cloudbase"; - if(int(cloudbase)/1000) { - tempstr1 = buf3; - transmission = transmission + " " + ConvertNumToSpokenDigits(tempstr1) + " thousand"; - } - if(((int)cloudbase % 1000)/100) { - tempstr1 = buf4; - transmission = transmission + " " + ConvertNumToSpokenDigits(tempstr1) + " hundred"; - } - transmission += " feet"; - } - - // Get the pressure / altimeter - double P = fgGetDouble("/environment/pressure-sea-level-inhg"); - if(ident.substr(0,2) == "EG" && fgGetBool("/sim/atc/use-millibars") == true) { - // Convert to millibars for the UK! - P *= 33.864; - sprintf(buf, "%.0f", P); - } else { - sprintf(buf, "%.2f", P); - } - transmission += " / Altimeter "; - tempstr1 = buf; - transmission += ConvertNumToSpokenDigits(tempstr1); - - // Based on the airport-id and wind get the active runway - //FGRunway *r; - double speed = stationweather.get_wind_speed_kt(); - double hdg = stationweather.get_wind_from_heading_deg(); - if (speed == 0) { - hdg = 270; // This forces West-facing rwys to be used in no-wind situations - // which is consistent with Flightgear's initial setup. - transmission += " / Winds_light_and_variable"; - } else { - // FIXME: get gust factor in somehow - char buf5[10]; - char buf6[10]; - sprintf(buf5, "%i", int(speed)); - sprintf(buf6, "%i", int(hdg)); - tempstr1 = buf5; - tempstr2 = buf6; - transmission = transmission + " / Winds " + ConvertNumToSpokenDigits(tempstr1) + " knots from " - + ConvertNumToSpokenDigits(tempstr2) + " degrees"; - } - - string rwy_no = globals->get_runways()->search(ident, int(hdg)); - if(rwy_no != "NN") { - transmission += " / Landing_and_departing_runway "; - transmission += ConvertRwyNumToSpokenString(atoi(rwy_no.c_str())); - //cout << "in atis.cxx, r.rwy_no = " << rwy_no << " r.id = " << r->id << " r.heading = " << r->heading << endl; - } - - // Anything else? - - transmission += " / Advise_controller_on_initial_contact_you_have "; - transmission += phonetic_id_string; - transmission += " /// "; +// Generate the actual broadcast ATIS transmission. +// Regen means regenerate the /current/ transmission. +// Special means generate a new transmission, with a new sequence. +// Returns 1 if we actually generated something. +int FGATIS::GenTransmission(const int regen, const int special) { + + double tstamp = atof(fgGetString("sim/time/elapsed-sec")); + string BRK = ".\n"; + int sequence = current_commlist->GetAtisSequence(ident, tstamp, 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 + } + + const int bs(100); + char buf[bs]; + string time_str = fgGetString("sim/time/gmt-string"); + string hours, mins; + + FGEnvironment stationweather = + ((FGEnvironmentMgr *)globals->get_subsystem("environment")) + ->getEnvironment(lat, lon, 0.0); + + transmission = ""; + + // UK CAA radiotelephony manual indicated ATIS transmissions start with "This is ..." + // In the US they just start with the airport name. + /// transmission += "This_is "; + // transmitted station name. + //cout << "In atis.cxx, airport name = " << name << endl; + transmission += name; + transmission += " airport information "; + + // Add the sequence letter + // 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(); + //cout << "In atis.cxx, time " << time_str + // << " --> " << hours << ":" << mins << endl; + string phonetic_seq_string = GetPhoneticLetter(sequence); + transmission += phonetic_seq_string + BRK; + transmission += "Automated weather observation "; +// speak each digit separately: + transmission += ConvertNumToSpokenDigits(hours + mins); + transmission += " zulu weather" + BRK; + + transmission += "Wind: "; + double speed = stationweather.get_wind_speed_kt(); + double wind_dir = stationweather.get_wind_from_heading_deg(); + while (wind_dir <= 0) wind_dir += 360; +// The following isn't as bad a kludge as it might seem. +// It combines the magvar at the /aircraft/ location with +// the wind direction in the stationweather environment. +// But if the aircraft is close enough to the station to +// be receiving the ATIS signal, this should be a good-enough +// approximation. For more-distant aircraft, the wind_dir +// shouldn't be corrected anyway. +// The less-kludgy approach would be to use the magvar associated +// with the station, but that is not tabulated in the stationweather +// structure as it stands, and computing it would be expensive. +// Also note that as it stands, there is only one environment in +// the entire FG universe, so the aircraft environment is the same +// as the station environment anyway. + wind_dir -= fgGetDouble("/environment/magnetic-variation-deg"); // wind_dir now magnetic + if (speed == 0) { +// 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 { + // FIXME: get gust factor in somehow + snprintf(buf, bs, "%03.0f", 5*round(wind_dir/5)); + transmission += ConvertNumToSpokenDigits(buf); + + snprintf(buf, bs, "%1.0f", speed); + transmission += " at " + ConvertNumToSpokenDigits(buf) + BRK; + } + + int did_some(0); + int did_ceiling(0); + double ceiling; + for (int layer = 0; layer <= 4; layer++) { + snprintf(buf, bs, "/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); + double ceiling = int(fgGetDouble(buf) - elev); + if (ceiling > 12000) continue; + if (coverage == "scattered") { + if (!did_some) transmission += " Sky condition: "; + did_some++; + } else /* must be a ceiling */ if (!did_ceiling) { + transmission += " Ceiling: "; + did_ceiling++; + did_some++; + } else { + transmission += " "; + } + int cig00 = int(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 "; + } + if (cig00) { + snprintf(buf, bs, "%i", cig00); + transmission += ConvertNumToSpokenDigits(buf); + transmission += " hundred "; + } + } else { + // Should this be "sky obscured?" + transmission += " zero "; // not "zero hundred" + } + transmission += coverage + BRK; + } + + transmission += "Temperature: "; + int temp = int(round(FGAtmo().fake_t_vs_a_us(elev))); + if(temp < 0) { + transmission += "minus "; + } + snprintf(buf, bs, "%i", abs(temp)); + transmission += ConvertNumToSpokenDigits(buf); + transmission += " dewpoint "; + double dpsl = fgGetDouble("/environment/dewpoint-sea-level-degc"); + temp = int(round(FGAtmo().fake_dp_vs_a_us(dpsl, elev))); + if(temp < 0) { + transmission += "minus "; + } + snprintf(buf, bs, "%i", abs(temp)); + transmission += ConvertNumToSpokenDigits(buf) + BRK; + + + transmission += "Visibility: "; + double visibility = stationweather.get_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; + + transmission += "Altimeter: "; + double QNH; + double Psl = fgGetDouble("/environment/pressure-sea-level-inhg"); + { + using namespace atmodel; + QNH = FGAtmo().qnh(elev, FGAtmo().p_vs_a(elev*foot, Psl*inHg) / inHg); + } + if(ident.substr(0,2) == "EG" && fgGetBool("/sim/atc/use-millibars")) { + // Convert to millibars for the UK! + QNH *= 33.864; + if (QNH > 1000) QNH -= 1000; + } else { + QNH *= 100; + } + snprintf(buf, bs, "%.0f", QNH); + transmission += ConvertNumToSpokenDigits(buf) + BRK; + + + int has_tower(1); // FIXME: assume all have towers for now.. + if (has_tower) { + string rwy_no = globals->get_runways()->search(ident, int(wind_dir)); + if(rwy_no != "NN") { + transmission += "Landing and departing runway "; + transmission += ConvertRwyNumToSpokenString(rwy_no) + BRK; + if (msg_OK) { + msg_time = cur_time; + //cout << "In atis.cxx, r.rwy_no: " << rwy_no + // << " wind_dir: " << wind_dir << endl; + } + } + transmission += "On initial contact advise you have information "; + transmission += phonetic_seq_string; + transmission += "... " + BRK; + } +#ifdef ATIS_TEST + cout << "**** ATIS active on:"; +#endif + for (map::iterator act = active_on.begin(); act != active_on.end(); act++){ + string prop = "/instrumentation/" + act->first + "/atis"; + globals->get_props()->setStringValue(prop.c_str(), transmission.c_str()); +#ifdef ATIS_TEST + cout << " " << prop; +#endif + } +#ifdef ATIS_TEST + cout << " ****" << endl; + cout << transmission << endl; +// Note that even if we aren't outputting the transmission +// on stdout, you can still see it by pointing a web browser +// at the property tree. The second comm radio is: +// http://localhost:5400/instrumentation/comm%5B1%5D +#endif + +// Take the previous English-looking string and munge it to +// be relatively-more acceptable to the primitive tts system. +// Note that : ; and . are among the token-delimeters recognized +// by the tts system. + for (unsigned int where;;) { + where = transmission.find_first_of(":."); + if (where == string::npos) break; + transmission.replace(where, 1, " /_ "); + } + return 1; } diff --git a/src/ATC/atis.hxx b/src/ATC/atis.hxx index 05835d4..6cc083e 100644 --- a/src/ATC/atis.hxx +++ b/src/ATC/atis.hxx @@ -50,6 +50,7 @@ SG_USING_STD(string); //DCL - a complete guess for now. #define FG_ATIS_DEFAULT_RANGE 30 + class FGATIS : public FGATC { //atc_type type; @@ -59,8 +60,15 @@ class FGATIS : public FGATC { // for failure modeling string trans_ident; // transmitted ident + double old_volume; bool atis_failed; // atis failed? + time_t msg_time; // for moderating error messages + time_t cur_time; + int msg_OK; + int attention; + bool _prev_display; // Previous value of _display flag + // Aircraft position // ATIS is actually a special case in that unlike other ATC eg.tower it doesn't actually know about // or the whereabouts of the aircraft it is transmitting to. However, to ensure consistancy of @@ -74,7 +82,9 @@ class FGATIS : public FGATC { FGATIS(void); ~FGATIS(void); - + virtual void Init(); + void attend (int); + //run the ATIS instance void Update(double dt); @@ -86,10 +96,12 @@ class FGATIS : public FGATC { string refname; // Holds the refname of a transmission in progress - //Update the transmission string - void UpdateTransmission(void); + int GenTransmission(const int regen, + const int special); // Generate the transmission string friend istream& operator>> ( istream&, FGATIS& ); }; +typedef int (FGATIS::*int_getter)() const; + #endif // _FG_ATIS_HXX diff --git a/src/ATC/commlist.cxx b/src/ATC/commlist.cxx index 4d27e1c..d41eba1 100644 --- a/src/ATC/commlist.cxx +++ b/src/ATC/commlist.cxx @@ -41,6 +41,7 @@ FGCommList *current_commlist; // Constructor FGCommList::FGCommList( void ) { + sg_srandom_time(); } @@ -293,54 +294,37 @@ bool FGCommList::FindByCode( const string& ICAO, ATCData& ad, atc_type tp ) { return false; } +// Normally the interval is 1 hour, +// but you can shorten it for testing. +const int INTERVAL(2*60); // TODO - this function should move somewhere else eventually! -// Return an appropriate call-sign for an ATIS transmission. -int FGCommList::GetCallSign( const string& apt_id, int hours, int mins ) +// Return an appropriate sequence number for an ATIS transmission. +// Return sequence number + 2600 if sequence is unchanged since +// last time. +int FGCommList::GetAtisSequence( const string& apt_id, + const double tstamp, const int special) { atis_transmission_type tran; - if(atislog.find(apt_id) == atislog.end()) { - // This station has not transmitted yet - return a random identifier - // and add the transmission to the log - tran.hours = hours; - tran.mins = mins; - sg_srandom_time(); - tran.callsign = int(sg_random() * 25) + 1; // This *should* give a random int between 1 and 26 - //atislog[apt_id].push_back(tran); - atislog[apt_id] = tran; - } else { - // This station has transmitted - calculate the appropriate identifier - // and add the transmission to the log if it has changed - tran = atislog[apt_id]; - // This next bit assumes that no-one comes back to the same ATIS station - // after running FlightGear for more than 24 hours !! - if((tran.hours == hours) && (tran.mins == mins)) { - return(tran.callsign); - } else { - if(tran.hours == hours) { - // The minutes must have changed - tran.mins = mins; - tran.callsign++; - } else { - if(hours < tran.hours) { - hours += 24; - } - tran.callsign += (hours - tran.hours); - if(mins != 0) { - // Assume transmissions were made on every hour - tran.callsign++; - } - tran.hours = hours; - tran.mins = mins; - } - // Wrap if we've exceeded Zulu - if(tran.callsign > 26) { - tran.callsign -= 26; - } - // And write the new transmission to the log - atislog[apt_id] = tran; - } - } - return(tran.callsign); + if(atislog.find(apt_id) == atislog.end()) { // New station + tran.tstamp = tstamp - INTERVAL; +// Random number between 0 and 25 inclusive, i.e. 26 equiprobable outcomes: + tran.sequence = int(sg_random() * LTRS); + atislog[apt_id] = tran; + //cout << "New ATIS station: " << apt_id << " seq-1: " + // << tran.sequence << endl; + } + +// calculate the appropriate identifier and update the log + tran = atislog[apt_id]; + + int delta = int((tstamp - tran.tstamp) / INTERVAL); + tran.tstamp += delta * INTERVAL; + if (special && !delta) delta++; // a "special" ATIS update is required + tran.sequence = (tran.sequence + delta) % LTRS; + atislog[apt_id] = tran; + //if (delta) cout << "New ATIS sequence: " << tran.sequence + // << " Delta: " << delta << endl; + return(tran.sequence + (delta ? 0 : LTRS*1000)); } diff --git a/src/ATC/commlist.hxx b/src/ATC/commlist.hxx index 1cb2f48..6a39971 100644 --- a/src/ATC/commlist.hxx +++ b/src/ATC/commlist.hxx @@ -99,9 +99,10 @@ public: // Find by Airport code. bool FindByCode( const string& ICAO, ATCData& ad, atc_type tp = INVALID ); - // Return the callsign for an ATIS transmission given transmission time and airport id - // This maybe should get moved somewhere else!! - int GetCallSign( const string& apt_id, int hours, int mins ); + // Return the sequence letter for an ATIS transmission given transmission time and airport id + // This maybe should get moved somewhere else!! + int GetAtisSequence( const string& apt_id, + const double tstamp, const int flush); private: @@ -119,9 +120,8 @@ private: // made in this session of FlightGear. This allows the callsign // to be allocated correctly wrt time. typedef struct { - int hours; - int mins; - int callsign; + double tstamp; + int sequence; } atis_transmission_type; typedef map < string, atis_transmission_type > atis_log_type; diff --git a/src/Instrumentation/altimeter.cxx b/src/Instrumentation/altimeter.cxx index 8e9fbb1..47e2728 100644 --- a/src/Instrumentation/altimeter.cxx +++ b/src/Instrumentation/altimeter.cxx @@ -28,13 +28,22 @@ #include "altimeter.hxx" + Altimeter::Altimeter ( SGPropertyNode *node, double quantum ) : _name(node->getStringValue("name", "altimeter")), _num(node->getIntValue("number", 0)), _static_pressure(node->getStringValue("static-pressure", "/systems/static/pressure-inhg")), _tau(node->getDoubleValue("tau", 0.1)), - _quantum(node->getDoubleValue("quantum", quantum)) -{} + _quantum(node->getDoubleValue("quantum", quantum)), + _press_alt(0.0) +{ +#ifdef ATMO_TEST + if (node->getIntValue("special", 0)) { + _altimeter.check_model(); + _altimeter.dump_stack(); + } +#endif +} Altimeter::~Altimeter () {} @@ -46,7 +55,6 @@ Altimeter::init () branch = "/instrumentation/" + _name; SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true ); - raw_PA = 0.0; _pressure_node = fgGetNode(_static_pressure.c_str(), true); _serviceable_node = node->getChild("serviceable", 0, true); _setting_node = node->getChild("setting-inhg", 0, true); @@ -65,17 +73,16 @@ Altimeter::update (double dt) double trat = _tau > 0 ? dt/_tau : 100; double pressure = _pressure_node->getDoubleValue(); double setting = _setting_node->getDoubleValue(); - double press_alt = _press_alt_node->getDoubleValue(); - // The mechanism settles slowly toward new pressure altitude: - raw_PA = fgGetLowPass(raw_PA, _altimeter.press_alt_ft(pressure), trat); - _mode_c_node->setDoubleValue(100 * SGMiscd::round(raw_PA/100)); - _kollsman = fgGetLowPass(_kollsman, _altimeter.kollsman_ft(setting), trat); - if (_quantum) - press_alt = _quantum * SGMiscd::round(raw_PA/_quantum); - else - press_alt = raw_PA; - _press_alt_node->setDoubleValue(press_alt); - _altitude_node->setDoubleValue(press_alt - _kollsman); +// The analog part of the mechanism settles slowly toward new pressure altitude: + _press_alt = fgGetLowPass(_press_alt, _altimeter.press_alt_ft(pressure), trat); + _mode_c_node->setDoubleValue(100 * SGMiscd::round(_press_alt / 100)); + + double PAQ = _press_alt; // quantized pressure altitude + if (_quantum) PAQ = _quantum * SGMiscd::round(_press_alt / _quantum); + _press_alt_node->setDoubleValue(PAQ); + + _kollsman = fgGetLowPass(_kollsman, _altimeter.kollsman_ft(setting), trat); + _altitude_node->setDoubleValue(PAQ - _kollsman); } } diff --git a/src/Instrumentation/altimeter.hxx b/src/Instrumentation/altimeter.hxx index e5baae6..712e3e2 100644 --- a/src/Instrumentation/altimeter.hxx +++ b/src/Instrumentation/altimeter.hxx @@ -12,7 +12,6 @@ #include #include - /** * Model a barometric altimeter tied to the static port. * @@ -31,7 +30,7 @@ class Altimeter : public SGSubsystem public: - Altimeter (SGPropertyNode *node, double quantum = 0); + Altimeter (SGPropertyNode *node, const double quantum = 0); virtual ~Altimeter (); virtual void init (); @@ -42,10 +41,10 @@ private: string _name; int _num; string _static_pressure; - double _tau; + double _tau; // response time in seconds double _quantum; double _kollsman; - double raw_PA; + double _press_alt; SGPropertyNode_ptr _serviceable_node; SGPropertyNode_ptr _setting_node;