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 "tiedpropertylist.hxx"
29 #include "metarproperties.hxx"
30 #include "metarairportfilter.hxx"
31 #include "fgmetar.hxx"
33 #include <Main/fg_props.hxx>
35 #include <simgear/structure/exception.hxx>
36 #include <simgear/misc/strutils.hxx>
38 #if defined(ENABLE_THREADS)
39 #include <OpenThreads/Thread>
40 #include <simgear/threads/SGQueue.hxx>
44 namespace Environment {
46 class BasicRealWxController : public RealWxController
49 BasicRealWxController( SGPropertyNode_ptr rootNode );
50 virtual ~BasicRealWxController ();
53 virtual void reinit ();
58 void update( double dt );
60 virtual void update( bool first, double dt ) = 0;
62 long getMetarMaxAgeMin() const { return _max_age_n == NULL ? 0 : _max_age_n->getLongValue(); }
64 SGPropertyNode_ptr _rootNode;
65 SGPropertyNode_ptr _longitude_n;
66 SGPropertyNode_ptr _latitude_n;
67 SGPropertyNode_ptr _ground_elevation_n;
68 SGPropertyNode_ptr _max_age_n;
72 TiedPropertyList _tiedProperties;
73 MetarProperties _metarProperties;
76 /* -------------------------------------------------------------------------------- */
79 ~/enabled: bool Enables/Disables the realwx controller
80 ~/metar[1..n]: string Target property path for metar data
83 BasicRealWxController::BasicRealWxController( SGPropertyNode_ptr rootNode ) :
85 _longitude_n( fgGetNode( "/position/longitude-deg", true )),
86 _latitude_n( fgGetNode( "/position/latitude-deg", true )),
87 _ground_elevation_n( fgGetNode( "/position/ground-elev-m", true )),
88 _max_age_n( fgGetNode( "/environment/params/metar-max-age-min", false ) ),
91 _metarProperties( fgGetNode( rootNode->getStringValue("metar", "/environment/metar"), true ) )
95 BasicRealWxController::~BasicRealWxController()
99 void BasicRealWxController::init()
102 update(0); // fetch data ASAP
105 void BasicRealWxController::reinit()
110 void BasicRealWxController::bind()
112 _tiedProperties.setRoot( _rootNode );
113 _tiedProperties.Tie( "enabled", &_enabled );
116 void BasicRealWxController::unbind()
118 _tiedProperties.Untie();
121 void BasicRealWxController::update( double dt )
124 update( !__enabled, dt );
131 /* -------------------------------------------------------------------------------- */
133 class NoaaMetarRealWxController : public BasicRealWxController {
135 NoaaMetarRealWxController( SGPropertyNode_ptr rootNode );
136 virtual ~NoaaMetarRealWxController();
137 virtual void update (bool first, double delta_time_sec);
139 class MetarLoadRequest {
141 MetarLoadRequest( const string & stationId ) {
142 _stationId = stationId;
143 _proxyHost = fgGetNode("/sim/presets/proxy/host", true)->getStringValue();
144 _proxyPort = fgGetNode("/sim/presets/proxy/port", true)->getStringValue();
145 _proxyAuth = fgGetNode("/sim/presets/proxy/authentication", true)->getStringValue();
154 double _metarTimeToLive;
155 double _positionTimeToLive;
156 double _minimumRequestInterval;
158 SGPropertyNode_ptr _metarDataNode;
159 SGPropertyNode_ptr _metarValidNode;
160 SGPropertyNode_ptr _metarStationIdNode;
163 #if defined(ENABLE_THREADS)
164 class MetarLoadThread : public OpenThreads::Thread {
166 MetarLoadThread( long maxAge );
167 void requestMetar( const MetarLoadRequest & metarRequest, bool background = true );
168 bool hasMetar() { return _responseQueue.size() > 0; }
169 string getMetar() { return _responseQueue.pop(); }
172 void fetch( const MetarLoadRequest & );
174 SGBlockingQueue <MetarLoadRequest> _requestQueue;
175 SGBlockingQueue <string> _responseQueue;
178 MetarLoadThread * _metarLoadThread;
182 NoaaMetarRealWxController::NoaaMetarRealWxController( SGPropertyNode_ptr rootNode ) :
183 BasicRealWxController(rootNode),
184 _metarTimeToLive(0.0),
185 _positionTimeToLive(0.0),
186 _minimumRequestInterval(0.0),
187 _metarDataNode(_metarProperties.get_root_node()->getNode("data",true)),
188 _metarValidNode(_metarProperties.get_root_node()->getNode("valid",true)),
189 _metarStationIdNode(_metarProperties.get_root_node()->getNode("station-id",true))
191 #if defined(ENABLE_THREADS)
192 _metarLoadThread = new MetarLoadThread(getMetarMaxAgeMin());
193 _metarLoadThread->start();
197 NoaaMetarRealWxController::~NoaaMetarRealWxController()
199 #if defined(ENABLE_THREADS)
200 if( _metarLoadThread ) {
201 MetarLoadRequest request("");
202 _metarLoadThread->requestMetar(request);
203 _metarLoadThread->join();
204 delete _metarLoadThread;
206 #endif // ENABLE_THREADS
209 void NoaaMetarRealWxController::update( bool first, double dt )
211 _metarTimeToLive -= dt;
212 _positionTimeToLive -= dt;
213 _minimumRequestInterval -= dt;
215 bool valid = _metarValidNode->getBoolValue();
216 string stationId = valid ? _metarStationIdNode->getStringValue() : "";
219 if( _metarTimeToLive <= 0.0 ) {
221 _metarTimeToLive = 900;
222 _positionTimeToLive = 0;
225 if( _positionTimeToLive <= 0.0 || valid == false ) {
226 _positionTimeToLive = 60.0;
228 SGGeod pos = SGGeod::fromDeg(_longitude_n->getDoubleValue(), _latitude_n->getDoubleValue());
230 FGAirport * nearestAirport = FGAirport::findClosest(pos, 10000.0, MetarAirportFilter::instance() );
231 if( nearestAirport == NULL ) {
232 SG_LOG(SG_ALL,SG_WARN,"RealWxController::update can't find airport with METAR within 10000NM" );
236 if( stationId != nearestAirport->ident() ) {
238 stationId = nearestAirport->ident();
244 if( _minimumRequestInterval <= 0 && stationId.length() > 0 ) {
245 MetarLoadRequest request( stationId );
246 // load the first metar in the foreground to make sure a metar is received
247 // before the automatic runway selection code runs. All subsequent calls
248 // run in the background
249 _metarLoadThread->requestMetar( request, !first );
250 _minimumRequestInterval = 10;
254 if( _metarLoadThread->hasMetar() ) {
255 string metar = _metarLoadThread->getMetar();
256 SG_LOG( SG_ALL, SG_ALERT, "NoaaMetarRwalWxController::update() received METAR " << metar );
257 _metarDataNode->setStringValue( metar );
263 /* -------------------------------------------------------------------------------- */
265 #if defined(ENABLE_THREADS)
266 NoaaMetarRealWxController::MetarLoadThread::MetarLoadThread( long maxAge ) :
271 void NoaaMetarRealWxController::MetarLoadThread::requestMetar( const MetarLoadRequest & metarRequest, bool background )
274 if( _requestQueue.size() > 10 ) {
275 SG_LOG(SG_ALL,SG_ALERT,
276 "NoaaMetarRealWxController::MetarLoadThread::requestMetar() more than 10 outstanding METAR requests, dropping "
277 << metarRequest._stationId );
281 _requestQueue.push( metarRequest );
283 fetch( metarRequest );
287 void NoaaMetarRealWxController::MetarLoadThread::run()
290 const MetarLoadRequest request = _requestQueue.pop();
292 if( request._stationId.size() == 0 )
299 void NoaaMetarRealWxController::MetarLoadThread::fetch( const MetarLoadRequest & request )
301 SGSharedPtr<FGMetar> result = NULL;
304 result = new FGMetar( request._stationId, request._proxyHost, request._proxyPort, request._proxyAuth );
305 } catch (const sg_io_exception& e) {
306 SG_LOG( SG_GENERAL, SG_WARN, "NoaaMetarRealWxController::fetchMetar(): can't get METAR for "
307 << request._stationId << ":" << e.getFormattedMessage().c_str() );
311 string reply = result->getData();
312 std::replace(reply.begin(), reply.end(), '\n', ' ');
313 string metar = simgear::strutils::strip( reply );
315 if( metar.empty() ) {
316 SG_LOG( SG_GENERAL, SG_WARN, "NoaaMetarRealWxController::fetchMetar(): dropping empty METAR for "
317 << request._stationId );
321 if( _maxAge && result->getAge_min() > _maxAge ) {
322 SG_LOG( SG_GENERAL, SG_ALERT, "NoaaMetarRealWxController::fetchMetar(): dropping outdated METAR "
327 _responseQueue.push( metar );
331 /* -------------------------------------------------------------------------------- */
333 RealWxController * RealWxController::createInstance( SGPropertyNode_ptr rootNode )
335 // string dataSource = rootNode->getStringValue("data-source", "noaa" );
336 // if( dataSource == "nwx" ) {
337 // return new NwxMetarRealWxController( rootNode );
339 return new NoaaMetarRealWxController( rootNode );
343 /* -------------------------------------------------------------------------------- */
345 } // namespace Environment