]> git.mxchange.org Git - flightgear.git/blob - src/Environment/realwx_ctrl.cxx
Reset: remove commands
[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, August 2011
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
29 #include <algorithm>
30 #include <boost/foreach.hpp>
31 #include <boost/algorithm/string/case_conv.hpp>
32
33 #include <simgear/structure/exception.hxx>
34 #include <simgear/misc/strutils.hxx>
35 #include <simgear/props/tiedpropertylist.hxx>
36 #include <simgear/io/HTTPMemoryRequest.hxx>
37 #include <simgear/timing/sg_time.hxx>
38 #include <simgear/structure/event_mgr.hxx>
39 #include <simgear/structure/commands.hxx>
40
41 #include "metarproperties.hxx"
42 #include "metarairportfilter.hxx"
43 #include "fgmetar.hxx"
44 #include <Network/HTTPClient.hxx>
45 #include <Main/fg_props.hxx>
46
47 namespace Environment {
48
49
50 /* -------------------------------------------------------------------------------- */
51
52 class MetarDataHandler {
53 public:
54     virtual void handleMetarData( const std::string & data ) = 0;
55 };
56
57 class MetarRequester {
58 public:
59     virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id ) = 0;
60 };
61
62 /* -------------------------------------------------------------------------------- */
63
64 class LiveMetarProperties : public MetarProperties, MetarDataHandler {
65 public:
66     LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge );
67     virtual ~LiveMetarProperties();
68     virtual void update( double dt );
69
70     virtual double getTimeToLive() const { return _timeToLive; }
71     virtual void resetTimeToLive()
72     { _timeToLive = 0.00; _pollingTimer = 0.0; }
73
74     // implementation of MetarDataHandler
75     virtual void handleMetarData( const std::string & data );
76
77     static const unsigned MAX_POLLING_INTERVAL_SECONDS = 10;
78     static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS = 900;
79
80 private:
81     double _timeToLive;
82     double _pollingTimer;
83     MetarRequester * _metarRequester;
84     int _maxAge;
85 };
86
87 typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
88
89 LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge ) :
90     MetarProperties( rootNode ),
91     _timeToLive(0.0),
92     _pollingTimer(0.0),
93     _metarRequester(metarRequester),
94     _maxAge(maxAge)
95 {
96     _tiedProperties.Tie("time-to-live", &_timeToLive );
97 }
98
99 LiveMetarProperties::~LiveMetarProperties()
100 {
101     _tiedProperties.Untie();
102 }
103
104 void LiveMetarProperties::update( double dt )
105 {
106     _timeToLive -= dt;
107     _pollingTimer -= dt;
108     if( _timeToLive <= 0.0 ) {
109         _timeToLive = 0.0;
110         std::string stationId = getStationId();
111         if( stationId.empty() ) return;
112         if( _pollingTimer > 0.0 ) return;
113         _metarRequester->requestMetar( this, stationId );
114         _pollingTimer = MAX_POLLING_INTERVAL_SECONDS;
115     }
116 }
117
118 void LiveMetarProperties::handleMetarData( const std::string & data )
119 {
120     SG_LOG( SG_ENVIRONMENT, SG_DEBUG, "LiveMetarProperties::handleMetarData() received METAR for " << getStationId() << ": " << data );
121     _timeToLive = DEFAULT_TIME_TO_LIVE_SECONDS;
122     
123     SGSharedPtr<FGMetar> m;
124     try {
125         m = new FGMetar(data.c_str());
126     }
127     catch( sg_io_exception ) {
128         SG_LOG( SG_ENVIRONMENT, SG_WARN, "Can't parse metar: " << data );
129         return;
130     }
131
132     if (_maxAge && (m->getAge_min() > _maxAge)) {
133         // METAR is older than max-age, ignore
134         SG_LOG( SG_ENVIRONMENT, SG_DEBUG, "Ignoring outdated METAR for " << getStationId());
135         return;
136     }
137     
138     setMetar( m );
139 }
140
141 /* -------------------------------------------------------------------------------- */
142
143 class BasicRealWxController : public RealWxController
144 {
145 public:
146     BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
147     virtual ~BasicRealWxController ();
148
149     virtual void init ();
150     virtual void reinit ();
151     virtual void shutdown ();
152     
153     /**
154      * Create a metar-property binding at the specified property path,
155      * and initiate a request for the specified station-ID (which may be
156      * empty). If the property path is already mapped, the station ID
157      * will be updated.
158      */
159     void addMetarAtPath(const string& propPath, const string& icao);
160   
161     void removeMetarAtPath(const string& propPath);
162 protected:
163     void bind();
164     void unbind();
165     void update( double dt );
166
167     void checkNearbyMetar();
168
169     long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
170
171     SGPropertyNode_ptr _rootNode;
172     SGPropertyNode_ptr _ground_elevation_n;
173     SGPropertyNode_ptr _max_age_n;
174
175     bool _enabled;
176     bool _wasEnabled;
177     simgear::TiedPropertyList _tiedProperties;
178     typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
179     MetarPropertiesList _metarProperties;
180     MetarRequester* _requester;
181
182 };
183
184 static bool commandRequestMetar(const SGPropertyNode* arg)
185 {
186   SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
187   if (!envMgr) {
188     return false;
189   }
190   
191   BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
192   if (!self) {
193     return false;
194   }
195   
196   string icao(arg->getStringValue("station"));
197   boost::to_upper(icao);
198   string path = arg->getStringValue("path");
199   self->addMetarAtPath(path, icao);
200   return true;
201 }
202   
203 static bool commandClearMetar(const SGPropertyNode* arg)
204 {
205   SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
206   if (!envMgr) {
207     return false;
208   }
209   
210   BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
211   if (!self) {
212     return false;
213   }
214   
215   string path = arg->getStringValue("path");
216   self->removeMetarAtPath(path);
217   return true;
218 }
219   
220 /* -------------------------------------------------------------------------------- */
221 /*
222 Properties
223  ~/enabled: bool              Enables/Disables the realwx controller
224  ~/metar[1..n]: string        Target property path for metar data
225  */
226
227 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
228   _rootNode(rootNode),
229   _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
230   _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
231   _enabled(true),
232   _wasEnabled(false),
233   _requester(metarRequester)
234 {
235     
236     SGCommandMgr::instance()->addCommand("request-metar", commandRequestMetar);
237     SGCommandMgr::instance()->addCommand("clear-metar", commandClearMetar);
238 }
239
240 BasicRealWxController::~BasicRealWxController()
241 {
242     SGCommandMgr::instance()->removeCommand("request-metar");
243     SGCommandMgr::instance()->removeCommand("clear-metar");
244 }
245
246 void BasicRealWxController::init()
247 {
248     _wasEnabled = false;
249     
250     // at least instantiate MetarProperties for /environment/metar
251     SGPropertyNode_ptr metarNode = fgGetNode( _rootNode->getStringValue("metar", "/environment/metar"), true );
252     _metarProperties.push_back( new LiveMetarProperties(metarNode,
253                                                         _requester,
254                                                         getMetarMaxAgeMin()));
255     
256     BOOST_FOREACH( SGPropertyNode_ptr n, _rootNode->getChildren("metar") ) {
257         SGPropertyNode_ptr metarNode = fgGetNode( n->getStringValue(), true );
258         addMetarAtPath(metarNode->getPath(), "");
259     }
260
261     checkNearbyMetar();
262     update(0); // fetch data ASAP
263     
264     globals->get_event_mgr()->addTask("checkNearbyMetar", this,
265                                       &BasicRealWxController::checkNearbyMetar, 60 );
266 }
267
268 void BasicRealWxController::reinit()
269 {
270     _wasEnabled = false;
271     checkNearbyMetar();
272     update(0); // fetch data ASAP
273 }
274     
275 void BasicRealWxController::shutdown()
276 {
277     globals->get_event_mgr()->removeTask("checkNearbyMetar");
278 }
279
280 void BasicRealWxController::bind()
281 {
282     _tiedProperties.setRoot( _rootNode );
283     _tiedProperties.Tie( "enabled", &_enabled );
284 }
285
286 void BasicRealWxController::unbind()
287 {
288     _tiedProperties.Untie();
289 }
290
291 void BasicRealWxController::update( double dt )
292 {  
293   if( _enabled ) {
294     bool firstIteration = !_wasEnabled;
295     // clock tick for every METAR in stock
296     BOOST_FOREACH(LiveMetarProperties* p, _metarProperties) {
297       // first round? All received METARs are outdated
298       if( firstIteration ) p->resetTimeToLive();
299       p->update(dt);
300     }
301
302     _wasEnabled = true;
303   } else {
304     _wasEnabled = false;
305   }
306 }
307
308 void BasicRealWxController::addMetarAtPath(const string& propPath, const string& icao)
309 {
310   // check for duplicate entries
311   BOOST_FOREACH( LiveMetarProperties_ptr p, _metarProperties ) {
312     if( p->get_root_node()->getPath() == propPath ) {
313       // already exists
314       if (p->getStationId() != icao) {
315         p->setStationId(icao);
316         p->resetTimeToLive();
317       }
318       
319       return;
320     }
321   } // of exitsing metar properties iteration
322
323   SGPropertyNode_ptr metarNode = fgGetNode(propPath, true);
324   SG_LOG( SG_ENVIRONMENT, SG_INFO, "Adding metar properties at " << propPath );
325   LiveMetarProperties_ptr p(new LiveMetarProperties( metarNode, _requester, getMetarMaxAgeMin() ));
326   _metarProperties.push_back(p);
327   p->setStationId(icao);
328 }
329
330 void BasicRealWxController::removeMetarAtPath(const string &propPath)
331 {
332   MetarPropertiesList::iterator it = _metarProperties.begin();
333   for (; it != _metarProperties.end(); ++it) {
334     LiveMetarProperties_ptr p(*it);
335     if( p->get_root_node()->getPath() == propPath ) {
336       _metarProperties.erase(it);
337       // final ref will drop, and delete the MetarProperties, when we return
338       return;
339     }
340   }
341   
342   SG_LOG(SG_ENVIRONMENT, SG_WARN, "no metar properties at " << propPath);
343 }
344   
345 void BasicRealWxController::checkNearbyMetar()
346 {
347     try {
348       const SGGeod & pos = globals->get_aircraft_position();
349
350       // check nearest airport
351       SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
352
353       FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
354       if( nearestAirport == NULL ) {
355           SG_LOG(SG_ENVIRONMENT,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM"  );
356           return;
357       }
358
359       SG_LOG(SG_ENVIRONMENT, SG_DEBUG, 
360           "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
361
362       // if it has changed, invalidate the associated METAR
363       if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
364           SG_LOG(SG_ENVIRONMENT, SG_INFO, 
365               "NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" << 
366               _metarProperties[0]->getStationId() <<
367               "', new: '" << nearestAirport->ident() << "'" );
368           _metarProperties[0]->setStationId( nearestAirport->ident() );
369           _metarProperties[0]->resetTimeToLive();
370       }
371     }
372     catch( sg_exception & ) {
373       return;
374     }
375     
376 }
377
378 /* -------------------------------------------------------------------------------- */
379
380 class NoaaMetarRealWxController : public BasicRealWxController, MetarRequester {
381 public:
382     NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
383
384     // implementation of MetarRequester
385     virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id );
386
387     virtual ~NoaaMetarRealWxController()
388     {
389     }
390 private:
391     
392 };
393
394 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
395   BasicRealWxController(rootNode, this )
396 {
397 }
398
399 void NoaaMetarRealWxController::requestMetar
400 (
401   MetarDataHandler* metarDataHandler,
402   const std::string& id
403 )
404 {
405   static const std::string NOAA_BASE_URL =
406     "http://weather.noaa.gov/pub/data/observations/metar/stations/";
407   class NoaaMetarGetRequest:
408     public simgear::HTTP::MemoryRequest
409   {
410     public:
411       NoaaMetarGetRequest( MetarDataHandler* metarDataHandler,
412                            const std::string& stationId ):
413         MemoryRequest(NOAA_BASE_URL + stationId + ".TXT"),
414         _metarDataHandler(metarDataHandler)
415       {
416         std::ostringstream buf;
417         buf <<  globals->get_time_params()->get_cur_time();
418         requestHeader("X-TIME") = buf.str();
419       }
420
421       virtual void onDone()
422       {
423         if( responseCode() != 200 )
424         {
425           SG_LOG
426           (
427             SG_ENVIRONMENT,
428             SG_WARN,
429             "metar download failed:" << url() << ": reason:" << responseReason()
430           );
431           return;
432         }
433
434         _metarDataHandler->handleMetarData
435         (
436           simgear::strutils::simplify(responseBody())
437         );
438       }
439
440       virtual void onFail()
441       {
442         SG_LOG(SG_ENVIRONMENT, SG_INFO, "metar download failure");
443       }
444
445     private:
446       MetarDataHandler * _metarDataHandler;
447   };
448
449   string upperId = boost::to_upper_copy(id);
450
451   SG_LOG
452   (
453     SG_ENVIRONMENT,
454     SG_INFO,
455     "NoaaMetarRealWxController::update(): "
456     "spawning load request for station-id '" << upperId << "'"
457   );
458   FGHTTPClient* http = static_cast<FGHTTPClient*>(globals->get_subsystem("http"));
459   if (http) {
460       http->makeRequest(new NoaaMetarGetRequest(metarDataHandler, upperId));
461   }
462 }
463
464 /* -------------------------------------------------------------------------------- */
465     
466 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
467 {
468   return new NoaaMetarRealWxController( rootNode );
469 }
470     
471 RealWxController::~RealWxController()
472 {
473 }
474
475 /* -------------------------------------------------------------------------------- */
476
477 } // namespace Environment