FlightGear next
realwx_ctrl.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: realwx_ctrl.cxx
3 * SPDX-FileComment: Process real weather data
4 * SPDX-FileCopyrightText: Copyright (C) 2002 David Megginson - david@megginson.com
5 * SPDX-FileContributor: Rewritten by Torsten Dreyer, August 2010, August 2011
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#ifdef HAVE_CONFIG_H
10# include "config.h"
11#endif
12
13#include "realwx_ctrl.hxx"
14
15#include <algorithm>
16#include <cctype>
17
18#include <simgear/structure/exception.hxx>
19#include <simgear/misc/strutils.hxx>
20#include <simgear/props/tiedpropertylist.hxx>
21#include <simgear/io/HTTPMemoryRequest.hxx>
22#include <simgear/timing/sg_time.hxx>
23#include <simgear/structure/event_mgr.hxx>
24#include <simgear/structure/commands.hxx>
25
26#include "metarproperties.hxx"
28#include "fgmetar.hxx"
30#include <Main/fg_props.hxx>
32
33namespace Environment {
34
35
36/* -------------------------------------------------------------------------------- */
37
38class MetarRequester;
39
40/* -------------------------------------------------------------------------------- */
41
43public:
44 LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge );
45 virtual ~LiveMetarProperties();
46 virtual void update( double dt );
47
48 virtual double getTimeToLive() const { return _timeToLive; }
49 virtual void resetTimeToLive()
50 { _timeToLive = 0.00; _pollingTimer = 0.0; }
51
52 // implementation of MetarDataHandler
53 virtual void handleMetarData( const std::string & data );
54 virtual void handleMetarFailure();
55
56 static const unsigned MAX_POLLING_INTERVAL_SECONDS = 10;
57 static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS = 900;
58
59private:
60 double _timeToLive;
61 double _pollingTimer;
62 MetarRequester * _metarRequester;
63 int _maxAge;
64 bool _failure;
65};
66
67typedef SGSharedPtr<LiveMetarProperties> LiveMetarProperties_ptr;
68
70public:
71 virtual void requestMetar( LiveMetarProperties_ptr metarDataHandler, const std::string & id ) = 0;
72};
73
74
75LiveMetarProperties::LiveMetarProperties( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester, int maxAge ) :
76 MetarProperties( rootNode ),
77 _timeToLive(0.0),
78 _pollingTimer(0.0),
79 _metarRequester(metarRequester),
80 _maxAge(maxAge),
81 _failure(false)
82{
83 _tiedProperties.Tie("time-to-live", &_timeToLive );
84 _tiedProperties.Tie("failure", &_failure);
85}
86
91
93{
94 _timeToLive -= dt;
95 _pollingTimer -= dt;
96 if( _timeToLive <= 0.0 ) {
97 _timeToLive = 0.0;
98 invalidate();
99 std::string stationId = getStationId();
100 if( stationId.empty() ) return;
101 if( _pollingTimer > 0.0 ) return;
102 _metarRequester->requestMetar( this, stationId );
103 _pollingTimer = MAX_POLLING_INTERVAL_SECONDS;
104 }
105}
106
107void LiveMetarProperties::handleMetarData( const std::string & data )
108{
109 SG_LOG( SG_ENVIRONMENT, SG_DEBUG, "LiveMetarProperties::handleMetarData() received METAR for " << getStationId() << ": " << data );
110 _timeToLive = DEFAULT_TIME_TO_LIVE_SECONDS;
111
112 SGSharedPtr<FGMetar> m;
113 static bool haveReportedMETARFailure = false;
114 try {
115 m = new FGMetar(data.c_str());
116 }
117 catch( sg_io_exception &e) {
118 SG_LOG( SG_ENVIRONMENT, SG_WARN, "Can't parse metar: " << data <<
119 " (" << e.getFormattedMessage() << ")");
120
121 // ensure we only report one METAR parse failure per session
122 if (!haveReportedMETARFailure) {
123 haveReportedMETARFailure = true;
124 flightgear::sentryReportException("Failed to parse live METAR", data);
125 }
126 _failure = true;
127 return;
128 }
129
130 if (_maxAge && (m->getAge_min() > _maxAge)) {
131 // METAR is older than max-age, ignore
132 SG_LOG( SG_ENVIRONMENT, SG_ALERT, "Ignoring outdated METAR for " << getStationId() << " (see /environment/params/metar-max-age-min)");
133 return;
134 }
135
136 _failure = false;
137 setMetar( m );
138}
139
141{
142 _failure = true;
143}
144
145/* -------------------------------------------------------------------------------- */
146
148{
149public:
150 BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester );
151 virtual ~BasicRealWxController ();
152
153 // Subsystem API.
154 void bind() override;
155 void init() override;
156 void reinit() override;
157 void shutdown() override;
158 void unbind() override;
159 void update(double dt) override;
160
167 void addMetarAtPath(const std::string& propPath, const std::string& icao);
168
169 void removeMetarAtPath(const std::string& propPath);
170
171 typedef std::vector<LiveMetarProperties_ptr> MetarPropertiesList;
172 MetarPropertiesList::iterator findMetarAtPath(const std::string &propPath);
173
174protected:
175 void checkNearbyMetar();
176
177 long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
178
179 SGPropertyNode_ptr _rootNode;
180 SGPropertyNode_ptr _ground_elevation_n;
181 SGPropertyNode_ptr _max_age_n;
182
185 simgear::TiedPropertyList _tiedProperties;
188};
189
190static bool commandRequestMetar(const SGPropertyNode * arg, SGPropertyNode * root)
191{
192 auto envMgr = (SGSubsystemGroup*) globals->get_subsystem_mgr()->get_subsystem("environment");
193 if (!envMgr) {
194 return false;
195 }
196
197 BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
198 if (!self) {
199 return false;
200 }
201
202 std::string icao(arg->getStringValue("station"));
203 std::transform(icao.begin(), icao.end(), icao.begin(), static_cast<int(*)(int)>(std::toupper));
204
205 std::string path = arg->getStringValue("path");
206 self->addMetarAtPath(path, icao);
207 return true;
208}
209
210static bool commandClearMetar(const SGPropertyNode * arg, SGPropertyNode * root)
211{
212 auto envMgr = (SGSubsystemGroup*) globals->get_subsystem_mgr()->get_subsystem("environment");
213 if (!envMgr) {
214 return false;
215 }
216
217 BasicRealWxController* self = (BasicRealWxController*) envMgr->get_subsystem("realwx");
218 if (!self) {
219 return false;
220 }
221
222 std::string path = arg->getStringValue("path");
223 self->removeMetarAtPath(path);
224 return true;
225}
226
227/* -------------------------------------------------------------------------------- */
228/*
229Properties
230 ~/enabled: bool Enables/Disables the realwx controller
231 ~/metar[1..n]: string Target property path for metar data
232 */
233
234BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode, MetarRequester * metarRequester ) :
235 _rootNode(rootNode),
236 _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
237 _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
238 _enabled(true),
239 _wasEnabled(false),
240 _requester(metarRequester)
241{
242
243 globals->get_commands()->addCommand("request-metar", commandRequestMetar);
244 globals->get_commands()->addCommand("clear-metar", commandClearMetar);
245}
246
248{
249 globals->get_commands()->removeCommand("request-metar");
250 globals->get_commands()->removeCommand("clear-metar");
251}
252
254{
255 _wasEnabled = false;
256
257 // at least instantiate MetarProperties for /environment/metar
258 SGPropertyNode_ptr metarNode = fgGetNode( _rootNode->getStringValue("metar", "/environment/metar"), true );
259 _metarProperties.push_back( new LiveMetarProperties(metarNode,
262
263 for( auto n : _rootNode->getChildren("metar") ) {
264 SGPropertyNode_ptr metarNode = fgGetNode( n->getStringValue(), true );
265 addMetarAtPath(metarNode->getPath(), "");
266 }
267
269 update(0); // fetch data ASAP
270
271 globals->get_event_mgr()->addTask("checkNearbyMetar",
272 [this](){ this->checkNearbyMetar(); }, 10 );
273}
274
276{
277 _wasEnabled = false;
279 update(0); // fetch data ASAP
280}
281
283{
284 globals->get_event_mgr()->removeTask("checkNearbyMetar");
285}
286
288{
289 _tiedProperties.setRoot( _rootNode );
290 _tiedProperties.Tie( "enabled", &_enabled );
291}
292
294{
295 _tiedProperties.Untie();
296}
297
299{
300 if( _enabled ) {
301 bool firstIteration = !_wasEnabled;
302 // clock tick for every METAR in stock
303 for(auto p : _metarProperties) {
304 // first round? All received METARs are outdated
305 if( firstIteration ) p->resetTimeToLive();
306 p->update(dt);
307 }
308
309 _wasEnabled = true;
310 } else {
311 _wasEnabled = false;
312 }
313}
314
315void BasicRealWxController::addMetarAtPath(const std::string& propPath, const std::string& icao)
316{
317 // check for duplicate entries
318 MetarPropertiesList::iterator it = findMetarAtPath(propPath);
319 if( it != _metarProperties.end() ) {
320 SG_LOG( SG_ENVIRONMENT, SG_INFO, "Reusing metar properties at " << propPath << " for " << icao);
321 // already exists
322 if ((*it)->getStationId() != icao) {
323 (*it)->setStationId(icao);
324 (*it)->resetTimeToLive();
325 }
326 return;
327 }
328
329 SGPropertyNode_ptr metarNode = fgGetNode(propPath, true);
330 SG_LOG( SG_ENVIRONMENT, SG_INFO, "Adding metar properties at " << propPath << " for " << icao);
332 _metarProperties.push_back(p);
333 p->setStationId(icao);
334}
335
336void BasicRealWxController::removeMetarAtPath(const std::string &propPath)
337{
338 MetarPropertiesList::iterator it = findMetarAtPath( propPath );
339 if( it != _metarProperties.end() ) {
340 SG_LOG(SG_ENVIRONMENT, SG_INFO, "removing metar properties at " << propPath);
341 _metarProperties.erase(it);
342 } else {
343 SG_LOG(SG_ENVIRONMENT, SG_WARN, "no metar properties at " << propPath);
344 }
345}
346
347BasicRealWxController::MetarPropertiesList::iterator BasicRealWxController::findMetarAtPath(const std::string &propPath)
348{
349 // don not compare unprocessed property path
350 // /foo/bar[0]/baz equals /foo/bar/baz
351 SGPropertyNode_ptr n = fgGetNode(propPath,false);
352 if( !n.valid() ) // trivial: node does not exist
353 return _metarProperties.end();
354
355 MetarPropertiesList::iterator it = _metarProperties.begin();
356 while( it != _metarProperties.end() &&
357 (*it)->get_root_node()->getPath() != n->getPath() )
358 ++it;
359
360 return it;
361}
362
364{
365 try {
366 const SGGeod & pos = globals->get_aircraft_position();
367
368 // check nearest airport
369 SG_LOG(SG_ENVIRONMENT, SG_DEBUG, "NoaaMetarRealWxController::update(): (re) checking nearby airport with METAR" );
370
371 FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
372 if( nearestAirport == NULL ) {
373 SG_LOG(SG_ENVIRONMENT,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM" );
374 return;
375 }
376
377 SG_LOG(SG_ENVIRONMENT, SG_DEBUG,
378 "NoaaMetarRealWxController::update(): nearest airport with METAR is: " << nearestAirport->ident() );
379
380 // if it has changed, invalidate the associated METAR
381 if( _metarProperties[0]->getStationId() != nearestAirport->ident() ) {
382 SG_LOG(SG_ENVIRONMENT, SG_INFO,
383 "NoaaMetarRealWxController::update(): nearest airport with METAR has changed. Old: '" <<
384 _metarProperties[0]->getStationId() <<
385 "', new: '" << nearestAirport->ident() << "'" );
386 _metarProperties[0]->setStationId( nearestAirport->ident() );
387 _metarProperties[0]->resetTimeToLive();
388 }
389 }
390 catch( sg_exception & ) {
391 return;
392 }
393}
394
395
396/* -------------------------------------------------------------------------------- */
397
399{
400public:
401 NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
402
403 // implementation of MetarRequester
404 virtual void requestMetar( LiveMetarProperties_ptr metarDataHandler, const std::string & id );
405
407 {
408 }
409
410 // Subsystem identification.
411 static const char* staticSubsystemClassId() { return "noaa-metar-real-wx-controller"; }
412
413private:
414 std::string noaa_base_url;
415};
416
418 BasicRealWxController(rootNode, this )
419{
420 // default to hardcoded URL for compatibility
421 noaa_base_url = "https://tgftp.nws.noaa.gov/data/observations/metar/stations/[station].TXT";
422
423 // override with environment/realwx/metar-url (if present)
424 SGPropertyNode *urlNode = _rootNode->getNode("metar-url", false);
425 if (urlNode != nullptr)
426 noaa_base_url = urlNode->getStringValue();
427}
428
430(
431 LiveMetarProperties_ptr metarDataHandler,
432 const std::string& id
433)
434{
435 class NoaaMetarGetRequest:
436 public simgear::HTTP::MemoryRequest
437 {
438 public:
439 NoaaMetarGetRequest( LiveMetarProperties_ptr metarDataHandler,
440 const std::string& stationId,
441 const std::string &base_url):
442 MemoryRequest( simgear::strutils::replace(base_url, "[station]",stationId) ),
443 _metarDataHandler(metarDataHandler)
444 {
445 std::ostringstream buf;
446 buf << globals->get_time_params()->get_cur_time();
447 requestHeader("X-TIME") = buf.str();
448 }
449
450 virtual void onDone()
451 {
452 if( responseCode() != 200 )
453 {
454 SG_LOG
455 (
456 SG_ENVIRONMENT,
457 SG_WARN,
458 "metar download failed:" << url() << ": reason:" << responseReason()
459 );
460 return;
461 }
462
463 _metarDataHandler->handleMetarData
464 (
465 simgear::strutils::simplify(responseBody())
466 );
467 }
468
469 virtual void onFail()
470 {
471 SG_LOG(SG_ENVIRONMENT, SG_INFO, "metar download failure");
472 _metarDataHandler->handleMetarFailure();
473 }
474
475 private:
476 LiveMetarProperties_ptr _metarDataHandler;
477 };
478
479 std::string upperId = id;
480 std::transform(upperId.begin(), upperId.end(), upperId.begin(), static_cast<int(*)(int)>(std::toupper));
481
482 SG_LOG
483 (
484 SG_ENVIRONMENT,
485 SG_INFO,
486 "NoaaMetarRealWxController::update(): "
487 "spawning load request for station-id '" << upperId << "'"
488 );
489 auto http = globals->get_subsystem<FGHTTPClient>();
490 if (http) {
491 http->makeRequest(new NoaaMetarGetRequest(metarDataHandler, upperId, noaa_base_url));
492 }
493}
494
495// Register the subsystem.
496#if 0
497SGSubsystemMgr::Registrant<NoaaMetarRealWxController> registrantNoaaMetarRealWxController(
498 SGSubsystemMgr::GENERAL,
499 {{"environment", SGSubsystemMgr::Dependency::SOFT},
500 {"FGHTTPClient", SGSubsystemMgr::Dependency::SOFT},
501 {"realwx", SGSubsystemMgr::Dependency::SOFT}});
502#endif
503
504
505/* -------------------------------------------------------------------------------- */
506
508{
509 return new NoaaMetarRealWxController( rootNode );
510}
511
515
516/* -------------------------------------------------------------------------------- */
517
518} // namespace Environment
#define p(x)
void addMetarAtPath(const std::string &propPath, const std::string &icao)
Create a metar-property binding at the specified property path, and initiate a request for the specif...
std::vector< LiveMetarProperties_ptr > MetarPropertiesList
MetarPropertiesList::iterator findMetarAtPath(const std::string &propPath)
void removeMetarAtPath(const std::string &propPath)
BasicRealWxController(SGPropertyNode_ptr rootNode, MetarRequester *metarRequester)
simgear::TiedPropertyList _tiedProperties
void update(double dt) override
static const unsigned MAX_POLLING_INTERVAL_SECONDS
static const unsigned DEFAULT_TIME_TO_LIVE_SECONDS
virtual void handleMetarData(const std::string &data)
virtual void update(double dt)
virtual double getTimeToLive() const
LiveMetarProperties(SGPropertyNode_ptr rootNode, MetarRequester *metarRequester, int maxAge)
static MetarAirportFilter * instance()
virtual const std::string & getStationId() const
MetarProperties(SGPropertyNode_ptr rootNode)
virtual void setMetar(SGSharedPtr< FGMetar > m)
simgear::TiedPropertyList _tiedProperties
virtual void requestMetar(LiveMetarProperties_ptr metarDataHandler, const std::string &id)=0
NoaaMetarRealWxController(SGPropertyNode_ptr rootNode)
static const char * staticSubsystemClassId()
virtual void requestMetar(LiveMetarProperties_ptr metarDataHandler, const std::string &id)
static RealWxController * createInstance(SGPropertyNode_ptr rootNode)
static FGAirportRef findClosest(const SGGeod &aPos, double aCuttofNm, Filter *filter=NULL)
Syntactic wrapper around FGPositioned::findClosest - find the closest match for filter,...
Definition airport.cxx:425
const std::string & ident() const
FGGlobals * globals
Definition globals.cxx:142
static bool commandRequestMetar(const SGPropertyNode *arg, SGPropertyNode *root)
static bool commandClearMetar(const SGPropertyNode *arg, SGPropertyNode *root)
SGSharedPtr< LiveMetarProperties > LiveMetarProperties_ptr
void sentryReportException(const std::string &, const std::string &)
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27