1 // realwx_ctrl.cxx -- Process real weather data
3 // Written by David Megginson, started February 2002.
4 // Rewritten by Torsten Dreyer, August 2010
6 // Copyright (C) 2002 David Megginson - david@megginson.com
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.
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.
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.
27 #include "realwx_ctrl.hxx"
28 #include "metarproperties.hxx"
29 #include "metarairportfilter.hxx"
30 #include "fgmetar.hxx"
32 #include <Main/fg_props.hxx>
34 #include <simgear/structure/exception.hxx>
35 #include <simgear/misc/strutils.hxx>
36 #include <simgear/props/tiedpropertylist.hxx>
38 #if defined(ENABLE_THREADS)
39 #include <OpenThreads/Thread>
40 #include <simgear/threads/SGQueue.hxx>
43 using simgear::PropertyList;
45 namespace Environment {
47 /* -------------------------------------------------------------------------------- */
49 class LiveMetarProperties : public MetarProperties {
51 LiveMetarProperties( SGPropertyNode_ptr rootNode );
52 virtual ~LiveMetarProperties();
53 virtual void update( double dt );
55 virtual double getTimeToLive() const { return _timeToLive; }
56 virtual void setTimeToLive( double value ) { _timeToLive = value; }
62 typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
64 LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode ) :
65 MetarProperties( rootNode ),
68 _tiedProperties.Tie("time-to-live", &_timeToLive );
71 LiveMetarProperties::~LiveMetarProperties()
73 _tiedProperties.Untie();
76 void LiveMetarProperties::update( double dt )
79 if( _timeToLive < 0.0 ) _timeToLive = 0.0;
82 /* -------------------------------------------------------------------------------- */
84 class BasicRealWxController : public RealWxController
87 BasicRealWxController( SGPropertyNode_ptr rootNode );
88 virtual ~BasicRealWxController ();
91 virtual void reinit ();
96 void update( double dt );
98 virtual void update( bool first, double dt ) = 0;
100 long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
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;
110 simgear::TiedPropertyList _tiedProperties;
111 typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
112 MetarPropertiesList _metarProperties;
115 /* -------------------------------------------------------------------------------- */
118 ~/enabled: bool Enables/Disables the realwx controller
119 ~/metar[1..n]: string Target property path for metar data
122 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr 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 ) ),
131 // at least instantiate MetarProperties for /environment/metar
132 _metarProperties.push_back( new LiveMetarProperties(
133 fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ) ) );
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 )));
143 BasicRealWxController::~BasicRealWxController()
147 void BasicRealWxController::init()
150 update(0); // fetch data ASAP
153 void BasicRealWxController::reinit()
158 void BasicRealWxController::bind()
160 _tiedProperties.setRoot( _rootNode );
161 _tiedProperties.Tie( "enabled", &_enabled );
164 void BasicRealWxController::unbind()
166 _tiedProperties.Untie();
169 void BasicRealWxController::update( double dt )
172 bool firstIteration = !__enabled; // first iteration after being enabled?
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 );
182 update( firstIteration, dt );
189 /* -------------------------------------------------------------------------------- */
191 class NoaaMetarRealWxController : public BasicRealWxController {
193 NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
194 virtual ~NoaaMetarRealWxController();
195 virtual void update (bool first, double delta_time_sec);
196 virtual void shutdown ();
198 class MetarLoadRequest {
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())
206 MetarLoadRequest( const MetarLoadRequest & other ) :
207 _stationId(other._stationId),
208 _proxyHost(other._proxyAuth),
209 _proxyPort(other._proxyPort),
210 _proxyAuth(other._proxyAuth)
219 class MetarLoadResponse {
221 MetarLoadResponse( const string & stationId, const string metar ) {
222 _stationId = stationId;
225 MetarLoadResponse( const MetarLoadResponse & other ) {
226 _stationId = other._stationId;
227 _metar = other._metar;
233 double _positionTimeToLive;
234 double _requestTimer;
236 #if defined(ENABLE_THREADS)
237 class MetarLoadThread : public OpenThreads::Thread {
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(); }
247 void fetch( const MetarLoadRequest & );
249 long _minRequestInterval;
251 SGBlockingQueue <MetarLoadRequest> _requestQueue;
252 SGBlockingQueue <MetarLoadResponse> _responseQueue;
255 MetarLoadThread * _metarLoadThread;
259 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
260 BasicRealWxController(rootNode),
261 _positionTimeToLive(0.0),
264 #if defined(ENABLE_THREADS)
265 _metarLoadThread = new MetarLoadThread(getMetarMaxAgeMin());
266 _metarLoadThread->start();
270 void NoaaMetarRealWxController::shutdown()
272 #if defined(ENABLE_THREADS)
273 if( _metarLoadThread ) {
274 delete _metarLoadThread;
275 _metarLoadThread = NULL;
277 #endif // ENABLE_THREADS
280 NoaaMetarRealWxController::~NoaaMetarRealWxController()
284 void NoaaMetarRealWxController::update( bool first, double dt )
286 _positionTimeToLive -= dt;
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;
294 SGGeod pos = SGGeod::fromDeg(_longitude_n->getDoubleValue(), _latitude_n->getDoubleValue());
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" );
302 SG_LOG(SG_ALL, SG_INFO,
303 "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
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 );
316 if( _requestTimer <= 0.0 ) {
317 _requestTimer = 10.0;
319 for( MetarPropertiesList::iterator it = _metarProperties.begin();
320 it != _metarProperties.end(); it++ ) {
322 if( (*it)->getTimeToLive() > 0.0 ) continue;
323 const std::string & stationId = (*it)->getStationId();
324 if( stationId.empty() ) continue;
326 SG_LOG(SG_ALL, SG_INFO,
327 "NoaaMetarRealWxController::update(): spawning load request for station-id '" << stationId << "'" );
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 );
339 // pick all the received responses from the result queue and update the associated
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 )
348 (*it)->setTimeToLive(900);
349 (*it)->setMetar( metar._metar );
354 /* -------------------------------------------------------------------------------- */
356 #if defined(ENABLE_THREADS)
357 NoaaMetarRealWxController::MetarLoadThread::MetarLoadThread( long maxAge ) :
359 _minRequestInterval(2000),
364 void NoaaMetarRealWxController::MetarLoadThread::requestMetar( const MetarLoadRequest & metarRequest, bool 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 );
374 _requestQueue.push( metarRequest );
376 fetch( metarRequest );
380 void NoaaMetarRealWxController::MetarLoadThread::stop()
382 // set stop flag and wake up the thread with an empty request
384 MetarLoadRequest request("");
385 requestMetar(request);
389 void NoaaMetarRealWxController::MetarLoadThread::run()
391 SGTimeStamp lastRun = SGTimeStamp::fromSec(0);
393 SGTimeStamp dt = SGTimeStamp::now() - lastRun;
395 long delayMs = _minRequestInterval - dt.getSeconds() * 1000;
396 while (( delayMs > 0 ) && !_stop)
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 );
407 lastRun = SGTimeStamp::now();
409 const MetarLoadRequest request = _requestQueue.pop();
411 if (( request._stationId.size() == 0 ) || _stop)
418 void NoaaMetarRealWxController::MetarLoadThread::fetch( const MetarLoadRequest & request )
420 SGSharedPtr<FGMetar> result = NULL;
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;
434 string reply = result->getData();
435 std::replace(reply.begin(), reply.end(), '\n', ' ');
436 string metar = simgear::strutils::strip( reply );
438 if( metar.empty() ) {
439 SG_LOG( SG_GENERAL, SG_WARN, "NoaaMetarRealWxController::fetchMetar(): dropping empty METAR for "
440 << request._stationId );
444 if( _maxAge && result->getAge_min() > _maxAge ) {
445 SG_LOG( SG_GENERAL, SG_ALERT, "NoaaMetarRealWxController::fetchMetar(): dropping outdated METAR "
450 MetarLoadResponse response( request._stationId, metar );
451 _responseQueue.push( response );
455 /* -------------------------------------------------------------------------------- */
457 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
459 // string dataSource = rootNode->getStringValue("data-source", "noaa" );
460 // if( dataSource == "nwx" ) {
461 // return new NwxMetarRealWxController( rootNode );
463 return new NoaaMetarRealWxController( rootNode );
467 /* -------------------------------------------------------------------------------- */
469 } // namespace Environment