1 // realwx_ctrl.cxx -- Process real weather data
3 // Written by David Megginson, started February 2002.
4 // Rewritten by Torsten Dreyer, August 2010, August 2011
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"
30 #include <boost/foreach.hpp>
31 #include <boost/algorithm/string/case_conv.hpp>
33 #include <simgear/structure/exception.hxx>
34 #include <simgear/misc/strutils.hxx>
35 #include <simgear/props/tiedpropertylist.hxx>
36 #include <simgear/io/HTTPRequest.hxx>
37 #include <simgear/timing/sg_time.hxx>
38 #include <simgear/structure/event_mgr.hxx>
39 #include <simgear/structure/commands.hxx>
41 #include "metarproperties.hxx"
42 #include "metarairportfilter.hxx"
43 #include "fgmetar.hxx"
44 #include <Network/HTTPClient.hxx>
45 #include <Main/fg_props.hxx>
47 namespace Environment {
50 /* -------------------------------------------------------------------------------- */
52 class MetarDataHandler {
54 virtual void handleMetarData( const std::string & data ) = 0;
57 class MetarRequester {
59 virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id ) = 0;
62 /* -------------------------------------------------------------------------------- */
64 class LiveMetarProperties : public MetarProperties, MetarDataHandler {
66 LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
67 virtual ~LiveMetarProperties();
68 virtual void update( double dt );
70 virtual double getTimeToLive() const { return _timeToLive; }
71 virtual void setTimeToLive( double value ) { _timeToLive = value; }
73 // implementation of MetarDataHandler
74 virtual void handleMetarData( const std::string & data );
76 static const unsigned MAX_POLLING_INTERVAL_SECONDS = 10;
77 static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS = 900;
82 MetarRequester * _metarRequester;
85 typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
87 LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
88 MetarProperties( rootNode ),
91 _metarRequester(metarRequester)
93 _tiedProperties.Tie("time-to-live", &_timeToLive );
96 LiveMetarProperties::~LiveMetarProperties()
98 _tiedProperties.Untie();
101 void LiveMetarProperties::update( double dt )
105 if( _timeToLive < 0.0 ) {
107 std::string stationId = getStationId();
108 if( stationId.empty() ) return;
109 if( _pollingTimer > 0.0 ) return;
110 _metarRequester->requestMetar( this, stationId );
111 _pollingTimer = MAX_POLLING_INTERVAL_SECONDS;
115 void LiveMetarProperties::handleMetarData( const std::string & data )
117 SG_LOG( SG_ENVIRONMENT, SG_INFO, "LiveMetarProperties::handleMetarData() received METAR for " << getStationId() << ": " << data );
118 _timeToLive = DEFAULT_TIME_TO_LIVE_SECONDS;
122 /* -------------------------------------------------------------------------------- */
124 class BasicRealWxController : public RealWxController
127 BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
128 virtual ~BasicRealWxController ();
130 virtual void init ();
131 virtual void reinit ();
132 virtual void shutdown ();
135 * Create a metar-property binding at the specified property path,
136 * and initiate a request for the specified station-ID (which may be
137 * empty). If the property path is already mapped, the station ID
140 void addMetarAtPath(const string& propPath, const string& icao);
142 void removeMetarAtPath(const string& propPath);
146 void update( double dt );
148 void checkNearbyMetar();
150 long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
152 SGPropertyNode_ptr _rootNode;
153 SGPropertyNode_ptr _ground_elevation_n;
154 SGPropertyNode_ptr _max_age_n;
158 simgear::TiedPropertyList _tiedProperties;
159 typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
160 MetarPropertiesList _metarProperties;
161 MetarRequester* _requester;
165 static bool commandRequestMetar(const SGPropertyNode* arg)
167 SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
172 BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
177 string icao(arg->getStringValue("station"));
178 boost::to_upper(icao);
179 string path = arg->getStringValue("path");
180 self->addMetarAtPath(path, icao);
184 static bool commandClearMetar(const SGPropertyNode* arg)
186 SGSubsystemGroup* envMgr = (SGSubsystemGroup*) globals->get_subsystem("environment");
191 BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
196 string path = arg->getStringValue("path");
197 self->removeMetarAtPath(path);
201 /* -------------------------------------------------------------------------------- */
204 ~/enabled: bool Enables/Disables the realwx controller
205 ~/metar[1..n]: string Target property path for metar data
208 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
210 _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
211 _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
214 _requester(metarRequester)
216 // at least instantiate MetarProperties for /environment/metar
217 _metarProperties.push_back( new LiveMetarProperties(
218 fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ), metarRequester ));
220 BOOST_FOREACH( SGPropertyNode_ptr n, rootNode->getChildren("metar") ) {
221 SGPropertyNode_ptr metarNode = fgGetNode( n->getStringValue(), true );
222 addMetarAtPath(metarNode->getPath(), "");
225 SGCommandMgr::instance()->addCommand("request-metar", commandRequestMetar);
226 SGCommandMgr::instance()->addCommand("clear-metar", commandClearMetar);
229 BasicRealWxController::~BasicRealWxController()
231 //SGCommandMgr::instance()->removeCommand("request-metar");
234 void BasicRealWxController::init()
237 update(0); // fetch data ASAP
239 globals->get_event_mgr()->addTask("checkNearbyMetar", this,
240 &BasicRealWxController::checkNearbyMetar, 60 );
243 void BasicRealWxController::reinit()
248 void BasicRealWxController::shutdown()
250 globals->get_event_mgr()->removeTask("checkNearbyMetar");
253 void BasicRealWxController::bind()
255 _tiedProperties.setRoot( _rootNode );
256 _tiedProperties.Tie( "enabled", &_enabled );
259 void BasicRealWxController::unbind()
261 _tiedProperties.Untie();
264 void BasicRealWxController::update( double dt )
267 bool firstIteration = !_wasEnabled;
269 // clock tick for every METAR in stock
270 BOOST_FOREACH(LiveMetarProperties* p, _metarProperties) {
271 // first round? All received METARs are outdated
272 if( firstIteration ) p->setTimeToLive( 0.0 );
282 void BasicRealWxController::addMetarAtPath(const string& propPath, const string& icao)
284 // check for duplicate entries
285 BOOST_FOREACH( LiveMetarProperties_ptr p, _metarProperties ) {
286 if( p->get_root_node()->getPath() == propPath ) {
288 if (p->getStationId() != icao) {
289 p->setStationId(icao);
290 p->setTimeToLive(0.0);
295 } // of exitsing metar properties iteration
297 SGPropertyNode_ptr metarNode = fgGetNode(propPath, true);
298 SG_LOG( SG_ENVIRONMENT, SG_INFO, "Adding metar properties at " << propPath );
299 LiveMetarProperties_ptr p(new LiveMetarProperties( metarNode, _requester ));
300 _metarProperties.push_back(p);
301 p->setStationId(icao);
304 void BasicRealWxController::removeMetarAtPath(const string &propPath)
306 MetarPropertiesList::iterator it = _metarProperties.begin();
307 for (; it != _metarProperties.end(); ++it) {
308 LiveMetarProperties_ptr p(*it);
309 if( p->get_root_node()->getPath() == propPath ) {
310 _metarProperties.erase(it);
311 // final ref will drop, and delete the MetarProperties, when we return
316 SG_LOG(SG_ENVIRONMENT, SG_WARN, "no metar properties at " << propPath);
319 void BasicRealWxController::checkNearbyMetar()
322 const SGGeod & pos = globals->get_aircraft_position();
324 // check nearest airport
325 SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
327 FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
328 if( nearestAirport == NULL ) {
329 SG_LOG(SG_ENVIRONMENT,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM" );
333 SG_LOG(SG_ENVIRONMENT, SG_DEBUG,
334 "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
336 // if it has changed, invalidate the associated METAR
337 if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
338 SG_LOG(SG_ENVIRONMENT, SG_INFO,
339 "NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" <<
340 _metarProperties[0]->getStationId() <<
341 "', new: '" << nearestAirport->ident() << "'" );
342 _metarProperties[0]->setStationId( nearestAirport->ident() );
343 _metarProperties[0]->setTimeToLive( 0.0 );
346 catch( sg_exception & ) {
352 /* -------------------------------------------------------------------------------- */
354 class NoaaMetarRealWxController : public BasicRealWxController, MetarRequester {
356 NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
358 // implementation of MetarRequester
359 virtual void requestMetar( MetarDataHandler * metarDataHandler, const std::string & id );
365 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
366 BasicRealWxController(rootNode, this )
370 void NoaaMetarRealWxController::requestMetar( MetarDataHandler * metarDataHandler, const std::string & id )
372 class NoaaMetarGetRequest : public simgear::HTTP::Request
375 NoaaMetarGetRequest(MetarDataHandler* metarDataHandler, const string& stationId ) :
376 Request("http://weather.noaa.gov/pub/data/observations/metar/stations/" + stationId + ".TXT"),
378 _metarDataHandler(metarDataHandler)
382 virtual string_list requestHeaders() const
385 reply.push_back("X-TIME");
389 virtual std::string header(const std::string& name) const
393 if( name == "X-TIME" ) {
394 std::ostringstream buf;
395 buf << globals->get_time_params()->get_cur_time();
402 virtual void responseHeader(const string& key, const string& value)
404 if (key == "x-metarproxy") {
409 virtual void gotBodyData(const char* s, int n)
411 _metar += string(s, n);
414 virtual void responseComplete()
416 if (responseCode() == 200) {
417 _metarDataHandler->handleMetarData( simgear::strutils::simplify(_metar) );
419 SG_LOG(SG_ENVIRONMENT, SG_WARN, "metar download failed:" << url() << ": reason:" << responseReason());
423 // bool fromMetarProxy() const
424 // { return _fromProxy; }
428 MetarDataHandler * _metarDataHandler;
431 string upperId = boost::to_upper_copy(id);
433 SG_LOG(SG_ENVIRONMENT, SG_INFO,
434 "NoaaMetarRealWxController::update(): spawning load request for station-id '" << upperId << "'" );
435 FGHTTPClient* http = static_cast<FGHTTPClient*>(globals->get_subsystem("http"));
437 http->makeRequest(new NoaaMetarGetRequest(metarDataHandler, upperId));
441 /* -------------------------------------------------------------------------------- */
443 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
445 return new NoaaMetarRealWxController( rootNode );
448 /* -------------------------------------------------------------------------------- */
450 } // namespace Environment