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