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