]> git.mxchange.org Git - flightgear.git/blob - src/Environment/realwx_ctrl.cxx
Avoid crash and/or long delay on shutdown in METAR loader.
[flightgear.git] / src / Environment / realwx_ctrl.cxx
1 // realwx_ctrl.cxx -- Process real weather data
2 //
3 // Written by David Megginson, started February 2002.
4 // Rewritten by Torsten Dreyer, August 2010
5 //
6 // Copyright (C) 2002  David Megginson - david@megginson.com
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include "realwx_ctrl.hxx"
28 #include "tiedpropertylist.hxx"
29 #include "metarproperties.hxx"
30 #include "metarairportfilter.hxx"
31 #include "fgmetar.hxx"
32
33 #include <Main/fg_props.hxx>
34
35 #include <simgear/structure/exception.hxx>
36 #include <simgear/misc/strutils.hxx>
37 #include <algorithm>
38 #if defined(ENABLE_THREADS)
39 #include <OpenThreads/Thread>
40 #include <simgear/threads/SGQueue.hxx>
41 #endif
42
43
44 namespace Environment {
45
46 /* -------------------------------------------------------------------------------- */
47
48 class LiveMetarProperties : public MetarProperties {
49 public:
50     LiveMetarProperties( SGPropertyNode_ptr rootNode );
51     virtual ~LiveMetarProperties();
52     virtual void update( double dt );
53
54     virtual double getTimeToLive() const { return _timeToLive; }
55     virtual void setTimeToLive( double value ) { _timeToLive = value; }
56 private:
57     double _timeToLive;
58
59 };
60
61 typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
62
63 LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode ) :
64     MetarProperties( rootNode ),
65     _timeToLive(0.0)
66 {
67     _tiedProperties.Tie("time-to-live", &_timeToLive );
68 }
69
70 LiveMetarProperties::~LiveMetarProperties()
71 {
72 }
73
74 void LiveMetarProperties::update( double dt )
75 {
76     _timeToLive -= dt;
77     if( _timeToLive < 0.0 ) _timeToLive = 0.0;
78 }
79
80 /* -------------------------------------------------------------------------------- */
81
82 class BasicRealWxController : public RealWxController
83 {
84 public:
85     BasicRealWxController( SGPropertyNode_ptr rootNode );
86     virtual ~BasicRealWxController ();
87
88     virtual void init ();
89     virtual void reinit ();
90
91 protected:
92     void bind();
93     void unbind();
94     void update( double dt );
95
96     virtual void update( bool first, double dt ) = 0;
97
98     long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
99
100     SGPropertyNode_ptr _rootNode;
101     SGPropertyNode_ptr _longitude_n;
102     SGPropertyNode_ptr _latitude_n;
103     SGPropertyNode_ptr _ground_elevation_n;
104     SGPropertyNode_ptr _max_age_n;
105
106     bool _enabled;
107     bool __enabled;
108     TiedPropertyList _tiedProperties;
109  ;   typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
110     MetarPropertiesList _metarProperties;
111 };
112
113 /* -------------------------------------------------------------------------------- */
114 /*
115 Properties
116  ~/enabled: bool              Enables/Disables the realwx controller
117  ~/metar[1..n]: string        Target property path for metar data
118  */
119
120 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode ) :
121   _rootNode(rootNode),
122   _longitude_n( fgGetNode( "/position/longitude-deg", true )),
123   _latitude_n( fgGetNode( "/position/latitude-deg", true )),
124   _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
125   _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
126   _enabled(true),
127   __enabled(false)
128 {
129     // at least instantiate MetarProperties for /environment/metar
130     _metarProperties.push_back( new LiveMetarProperties( 
131             fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ) ) );
132
133     PropertyList metars = rootNode->getChildren("metar");
134     for( PropertyList::size_type i = 1; i < metars.size(); i++ ) {
135        SG_LOG( SG_ALL, SG_INFO, "Adding metar properties at " << metars[i]->getStringValue() );
136         _metarProperties.push_back( new LiveMetarProperties( 
137             fgGetNode( metars[i]->getStringValue(), true )));
138     }
139 }
140
141 BasicRealWxController::~BasicRealWxController()
142 {
143 }
144
145 void BasicRealWxController::init()
146 {
147     __enabled = false;
148     update(0); // fetch data ASAP
149 }
150
151 void BasicRealWxController::reinit()
152 {
153     __enabled = false;
154 }
155
156 void BasicRealWxController::bind()
157 {
158     _tiedProperties.setRoot( _rootNode );
159     _tiedProperties.Tie( "enabled", &_enabled );
160 }
161
162 void BasicRealWxController::unbind()
163 {
164     _tiedProperties.Untie();
165 }
166
167 void BasicRealWxController::update( double dt )
168 {
169   if( _enabled ) {
170     bool firstIteration = !__enabled; // first iteration after being enabled?
171
172     // clock tick for every METAR in stock
173     for( MetarPropertiesList::iterator it = _metarProperties.begin();
174           it != _metarProperties.end(); it++ ) {
175       // first round? All received METARs are outdated
176       if( firstIteration ) (*it)->setTimeToLive( 0.0 );
177       (*it)->update(dt);
178     }
179
180     update( firstIteration, dt );
181     __enabled = true;
182   } else {
183     __enabled = false;
184   }
185 }
186
187 /* -------------------------------------------------------------------------------- */
188
189 class NoaaMetarRealWxController : public BasicRealWxController {
190 public:
191     NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
192     virtual ~NoaaMetarRealWxController();
193     virtual void update (bool first, double delta_time_sec);
194     virtual void shutdown ();
195
196     class MetarLoadRequest {
197     public:
198         MetarLoadRequest( const string & stationId ) {
199             _stationId = stationId;
200             _proxyHost = fgGetNode("/sim/presets/proxy/host", true)->getStringValue();
201             _proxyPort = fgGetNode("/sim/presets/proxy/port", true)->getStringValue();
202             _proxyAuth = fgGetNode("/sim/presets/proxy/authentication", true)->getStringValue();
203         }
204         MetarLoadRequest( const MetarLoadRequest & other ) {
205             _stationId = other._stationId;
206             _proxyHost = other._proxyAuth;
207             _proxyPort = other._proxyPort;
208             _proxyAuth = other._proxyAuth;
209         }
210         string _stationId;
211         string _proxyHost;
212         string _proxyPort;
213         string _proxyAuth;
214     private:
215     };
216
217     class MetarLoadResponse {
218     public:
219         MetarLoadResponse( const string & stationId, const string metar ) {
220             _stationId = stationId;
221             _metar = metar;
222         }
223         MetarLoadResponse( const MetarLoadResponse & other ) {
224             _stationId = other._stationId;
225             _metar = other._metar;
226         }
227         string _stationId;
228         string _metar;
229     };
230 private:
231     double _positionTimeToLive;
232     double _requestTimer;
233
234 #if defined(ENABLE_THREADS)
235      class MetarLoadThread : public OpenThreads::Thread {
236      public:
237         MetarLoadThread( long maxAge );
238         virtual ~MetarLoadThread( ) { stop(); } 
239         void requestMetar( const MetarLoadRequest & metarRequest, bool background = true );
240         bool hasMetar() { return _responseQueue.size() > 0; }
241         MetarLoadResponse getMetar() { return _responseQueue.pop(); }
242         virtual void run();
243         void stop();
244      private:
245         void fetch( const MetarLoadRequest & );
246         long _maxAge;
247         long _minRequestInterval;
248         volatile bool _stop;
249         SGBlockingQueue <MetarLoadRequest> _requestQueue;
250         SGBlockingQueue <MetarLoadResponse> _responseQueue;
251      };
252
253      MetarLoadThread * _metarLoadThread;
254 #endif
255 };
256
257 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
258   BasicRealWxController(rootNode),
259   _positionTimeToLive(0.0),
260   _requestTimer(0.0)
261 {
262 #if defined(ENABLE_THREADS)
263     _metarLoadThread = new MetarLoadThread(getMetarMaxAgeMin());
264     _metarLoadThread->start();
265 #endif
266 }
267
268 void NoaaMetarRealWxController::shutdown()
269 {
270 #if defined(ENABLE_THREADS)
271     if( _metarLoadThread ) {
272         delete _metarLoadThread;
273         _metarLoadThread = NULL;
274     }
275 #endif // ENABLE_THREADS
276 }
277
278 NoaaMetarRealWxController::~NoaaMetarRealWxController()
279 {
280 }
281
282 void NoaaMetarRealWxController::update( bool first, double dt )
283 {
284     _positionTimeToLive -= dt;
285     _requestTimer -= dt;
286
287     if( _positionTimeToLive <= 0.0 ) {
288         // check nearest airport
289         SG_LOG(SG_ALL, SG_INFO, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
290         _positionTimeToLive = 60.0;
291
292         SGGeod pos = SGGeod::fromDeg(_longitude_n->getDoubleValue(), _latitude_n->getDoubleValue());
293
294         FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
295         if( nearestAirport == NULL ) {
296             SG_LOG(SG_ALL,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM"  );
297             return;
298         }
299
300         SG_LOG(SG_ALL, SG_INFO, 
301             "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
302
303         // if it has changed, invalidate the associated METAR
304         if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
305             SG_LOG(SG_ALL, SG_INFO, 
306                 "NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" << 
307                 _metarProperties[0]->getStationId() <<
308                 "', new: '" << nearestAirport->ident() << "'" );
309             _metarProperties[0]->setStationId( nearestAirport->ident() );
310             _metarProperties[0]->setTimeToLive( 0.0 );
311         }
312     }
313   
314     if( _requestTimer <= 0.0 ) {
315         _requestTimer = 10.0;
316
317         for( MetarPropertiesList::iterator it = _metarProperties.begin(); 
318             it != _metarProperties.end(); it++ ) {
319
320                 if( (*it)->getTimeToLive() > 0.0 ) continue;
321                 const std::string & stationId = (*it)->getStationId();
322                 if( stationId.empty() ) continue;
323
324                 SG_LOG(SG_ALL, SG_INFO, 
325                     "NoaaMetarRealWxController::update(): spawning load request for station-id '" << stationId << "'" );
326             
327                 MetarLoadRequest request( stationId );
328                 // load the metar for the nearest airport in the foreground if the fdm is uninitialized
329                 // to make sure a metar is received
330                 // before the automatic runway selection code runs. All subsequent calls
331                 // run in the background
332                 bool background = fgGetBool("/sim/fdm-initialized", false ) || it != _metarProperties.begin();
333                 _metarLoadThread->requestMetar( request, background );
334         }
335     }
336
337     // pick all the received responses from the result queue and update the associated
338     // property tree
339     while( _metarLoadThread->hasMetar() ) {
340         MetarLoadResponse metar = _metarLoadThread->getMetar();
341         SG_LOG( SG_ALL, SG_INFO, "NoaaMetarRwalWxController::update() received METAR for " << metar._stationId << ": " << metar._metar );
342         for( MetarPropertiesList::iterator it = _metarProperties.begin(); 
343             it != _metarProperties.end(); it++ ) {
344                 if( (*it)->getStationId() != metar._stationId )
345                     continue;
346                 (*it)->setTimeToLive(900);
347                 (*it)->setMetar( metar._metar );
348         }
349     }
350 }
351
352 /* -------------------------------------------------------------------------------- */
353
354 #if defined(ENABLE_THREADS)
355 NoaaMetarRealWxController::MetarLoadThread::MetarLoadThread( long maxAge ) :
356   _maxAge(maxAge),
357   _minRequestInterval(2000),
358   _stop(false)
359 {
360 }
361
362 void NoaaMetarRealWxController::MetarLoadThread::requestMetar( const MetarLoadRequest & metarRequest, bool background )
363 {
364     if( background ) {
365         if( _requestQueue.size() > 10 ) {
366             SG_LOG(SG_ALL,SG_ALERT,
367                 "NoaaMetarRealWxController::MetarLoadThread::requestMetar() more than 10 outstanding METAR requests, dropping " 
368                 << metarRequest._stationId );
369             return;
370         }
371
372         _requestQueue.push( metarRequest );
373     } else {
374         fetch( metarRequest );
375     }
376 }
377
378 void NoaaMetarRealWxController::MetarLoadThread::stop()
379 {
380     // set stop flag and wake up the thread with an empty request
381     _stop = true;
382     MetarLoadRequest request("");
383     requestMetar(request);
384     join();
385 }
386
387 void NoaaMetarRealWxController::MetarLoadThread::run()
388 {
389     SGTimeStamp lastRun = SGTimeStamp::fromSec(0);
390     for( ;; ) {
391         SGTimeStamp dt = SGTimeStamp::now() - lastRun;
392
393         long delayMs = _minRequestInterval - dt.getSeconds() * 1000;
394         while (( delayMs > 0 ) && !_stop)
395         {
396             // sleep no more than 3 seconds at a time, otherwise shutdown response is too slow
397             long sleepMs = (delayMs>3000) ? 3000 : delayMs; 
398             microSleep( sleepMs * 1000 );
399             delayMs -= sleepMs;
400         }
401
402         if (_stop)
403             break;
404
405         lastRun = SGTimeStamp::now();
406  
407         const MetarLoadRequest request = _requestQueue.pop();
408
409         if (( request._stationId.size() == 0 ) || _stop)
410             break;
411
412         fetch( request );
413     }
414 }
415
416 void NoaaMetarRealWxController::MetarLoadThread::fetch( const MetarLoadRequest & request )
417 {
418    SGSharedPtr<FGMetar> result = NULL;
419
420     try {
421         result = new FGMetar( request._stationId, request._proxyHost, request._proxyPort, request._proxyAuth );
422         _minRequestInterval = 2000;
423     } catch (const sg_io_exception& e) {
424         SG_LOG( SG_GENERAL, SG_WARN, "NoaaMetarRealWxController::fetchMetar(): can't get METAR for " 
425                                     << request._stationId << ":" << e.getFormattedMessage().c_str() );
426         _minRequestInterval += _minRequestInterval/2; 
427         if( _minRequestInterval > 30000 )
428             _minRequestInterval = 30000;
429         return;
430     }
431
432     string reply = result->getData();
433     std::replace(reply.begin(), reply.end(), '\n', ' ');
434     string metar = simgear::strutils::strip( reply );
435
436     if( metar.empty() ) {
437         SG_LOG( SG_GENERAL, SG_WARN, "NoaaMetarRealWxController::fetchMetar(): dropping empty METAR for " 
438                                     << request._stationId );
439         return;
440     }
441
442     if( _maxAge && result->getAge_min() > _maxAge ) {
443         SG_LOG( SG_GENERAL, SG_ALERT, "NoaaMetarRealWxController::fetchMetar(): dropping outdated METAR " 
444                                      << metar );
445         return;
446     }
447
448     MetarLoadResponse response( request._stationId, metar );
449     _responseQueue.push( response );
450 }
451 #endif
452
453 /* -------------------------------------------------------------------------------- */
454
455 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
456 {
457 //    string dataSource = rootNode->getStringValue("data-source", "noaa" );
458 //    if( dataSource == "nwx" ) {
459 //      return new NwxMetarRealWxController( rootNode );
460 //    } else {
461       return new NoaaMetarRealWxController( rootNode );
462 //    }
463 }
464
465 /* -------------------------------------------------------------------------------- */
466
467 } // namespace Environment