source: trunk/FACT++/src/tngweather.cc@ 18338

Last change on this file since 18338 was 18338, checked in by tbretz, 10 years ago
Adapted to the latest changed on the TNG weather report web site and to its new location.
File size: 19.0 KB
Line 
1#include <boost/array.hpp>
2
3#include <string> // std::string
4#include <algorithm> // std::transform
5#include <cctype> // std::tolower
6
7#include "FACT.h"
8#include "Dim.h"
9#include "Event.h"
10#include "Shell.h"
11#include "StateMachineDim.h"
12#include "StateMachineAsio.h"
13#include "Connection.h"
14#include "LocalControl.h"
15#include "Configuration.h"
16#include "Timers.h"
17#include "Console.h"
18
19#include "tools.h"
20
21#include "HeadersTNGWeather.h"
22
23#include <QtXml/QDomDocument>
24
25namespace ba = boost::asio;
26namespace bs = boost::system;
27namespace dummy = ba::placeholders;
28
29using namespace std;
30using namespace TNGWeather;
31
32
33class ConnectionWeather : public Connection
34{
35 uint16_t fInterval;
36
37 bool fIsVerbose;
38
39 string fSite;
40
41 virtual void UpdateWeather(const Time &, const DimWeather &)
42 {
43 }
44
45 virtual void UpdateDust(const Time &, const float &)
46 {
47 }
48
49 string fRdfData;
50 float fDust;
51
52protected:
53
54 boost::array<char, 4096> fArray;
55
56 Time fLastReport;
57 Time fLastReception;
58
59 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
60 {
61 // Do not schedule a new read if the connection failed.
62 if (bytes_received==0 || err)
63 {
64 if (err==ba::error::eof)
65 Warn("Connection closed by remote host.");
66
67 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
68 // 125: Operation canceled
69 if (err && err!=ba::error::eof && // Connection closed by remote host
70 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
71 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
72 {
73 ostringstream str;
74 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
75 Error(str);
76 }
77 PostClose(err!=ba::error::basic_errors::operation_aborted);
78
79 fRdfData = "";
80 return;
81 }
82
83 fRdfData += string(fArray.data(), bytes_received);
84
85 const size_t end = fRdfData.find("\r\n\r\n");
86 if (end==string::npos)
87 {
88 Out() << "Received data corrupted [1]." << endl;
89 Out() << fRdfData << endl;
90 return;
91 }
92
93 string data(fRdfData);
94 data.erase(0, end+4);
95
96 size_t pos = 0;
97 while (1)
98 {
99 const size_t chunk = data.find("\r\n", pos);
100 if (chunk==0 || chunk==string::npos)
101 {
102 StartReadReport();
103 return;
104 }
105
106 size_t len = 0;
107 stringstream val(data.substr(pos, chunk-pos));
108 val >> hex >> len;
109
110 data.erase(pos, chunk-pos+2);
111 if (len==0)
112 break;
113
114 pos += len+2; // Count trailing \r\n of chunk
115 }
116
117
118 fLastReception = Time();
119 fRdfData = "";
120 PostClose(false);
121
122 if (fIsVerbose)
123 {
124 Out() << "------------------------------------------------------" << endl;
125 Out() << data << endl;
126 Out() << "------------------------------------------------------" << endl;
127 }
128
129 QDomDocument doc;
130 if (!doc.setContent(QString(data.data()), false))
131 {
132 Warn("Parsing of xml failed [0].");
133 PostClose(false);
134 return;
135 }
136
137 if (fIsVerbose)
138 Out() << "Parsed:\n-------\n" << doc.toString().toStdString() << endl;
139
140 const QDomElement root = doc.documentElement();
141 const QDomElement channel = root.firstChildElement("channel");
142 const QDomElement item = channel.firstChildElement("item");
143
144 const QDomElement see = item.firstChildElement("tngw:dimmSeeing");
145 const QDomElement mjd = item.firstChildElement("tngw:dimmSeeing.date");
146 const QDomElement med = item.firstChildElement("tngw:dimmSeeing.median");
147 const QDomElement sdev = item.firstChildElement("tngw:dimmSeeing.stdev");
148 const QDomElement dust = item.firstChildElement("tngw:dustTotal");
149 const QDomElement trend = item.firstChildElement("tngw:trend");
150 const QDomElement pres = item.firstChildElement("tngw:airPressure");
151 const QDomElement dew = item.firstChildElement("tngw:dewPoint");
152 const QDomElement wdir = item.firstChildElement("tngw:windDirection");
153 const QDomElement speed = item.firstChildElement("tngw:windSpeed");
154 const QDomElement hum = item.firstChildElement("tngw:hum");
155 const QDomElement tmp = item.firstChildElement("tngw:temperature");
156 const QDomElement solar = item.firstChildElement("tngw:solarimeter");
157 const QDomElement date = item.firstChildElement("tngw:date");
158
159 if (see.isNull() || mjd.isNull() || med.isNull() || sdev.isNull() ||
160 dust.isNull() || trend.isNull() || pres.isNull() || dew.isNull() ||
161 wdir.isNull() || speed.isNull() || hum.isNull() || tmp.isNull() ||
162 solar.isNull()|| date.isNull())
163 {
164 Warn("Parsing of xml failed [1].");
165 PostClose(false);
166 return;
167 }
168
169 DimWeather w;
170 w.fSeeingMjd = Time(mjd.text().toStdString()).Mjd();
171 w.fSeeing = see .text().toFloat();
172 w.fSeeingMed = med .text().toFloat();
173 w.fSeeingStdev = sdev .text().toFloat();
174 w.fDustTotal = dust .text().toFloat();
175 w.fTrend = trend.text().toFloat();
176 w.fAirPressure = pres .text().toFloat();
177 w.fDewPoint = dew .text().toFloat();
178 w.fWindDirection = wdir .text().toFloat();
179 w.fWindSpeed = speed.text().toFloat()*3.6;
180 w.fHumidity = hum .text().toFloat();
181 w.fTemperature = tmp .text().toFloat();
182 w.fSolarimeter = solar.text().toFloat();
183
184 const string obj = date.text().toStdString();
185
186 Time time(obj);
187 if (!time.IsValid())
188 {
189 struct tm tm;
190
191 vector<char> buf(255);
192 if (strptime(obj.c_str(), "%c", &tm))
193 time = Time(tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
194 tm.tm_hour, tm.tm_min, tm.tm_sec);
195 }
196
197 if (!time.IsValid())
198 throw runtime_error("time invalid");
199
200 if (time!=fLastReport && fIsVerbose)
201 {
202 Out() << endl;
203 Out() << "Date: " << time << endl;
204 Out() << "Seeing: " << w.fSeeing << " [" << Time(w.fSeeingMjd) << "]" << endl;
205 Out() << "Seeing: " << w.fSeeingMed << " +- " << w.fSeeingStdev << endl;
206 Out() << "DustTotal: " << w.fDustTotal << endl;
207 Out() << "AirPressure: " << w.fAirPressure << endl;
208 Out() << "Trend: " << w.fTrend << endl;
209 Out() << "DewPoint: " << w.fDewPoint << endl;
210 Out() << "WindDirection: " << w.fWindDirection << endl;
211 Out() << "WindSpeed: " << w.fWindSpeed << endl;
212 Out() << "Humidity: " << w.fHumidity << endl;
213 Out() << "Temperature: " << w.fTemperature << endl;
214 Out() << "Solarimeter: " << w.fSolarimeter << endl;
215 Out() << endl;
216 }
217
218 fLastReport = time;
219
220 UpdateWeather(time, w);
221
222 if (fDust==w.fDustTotal)
223 return;
224
225 UpdateDust(time, w.fDustTotal);
226 fDust = w.fDustTotal;
227
228 ostringstream out;
229 out << setprecision(3) << "Dust: " << fDust << "ug/m^3 [" << time << "]";
230 Message(out);
231 }
232
233 void StartReadReport()
234 {
235 async_read_some(ba::buffer(fArray),
236 boost::bind(&ConnectionWeather::HandleRead, this,
237 dummy::error, dummy::bytes_transferred));
238 }
239
240 boost::asio::deadline_timer fKeepAlive;
241
242 void PostRequest()
243 {
244 const string cmd =
245 "GET "+fSite+" HTTP/1.1\r\n"
246 "User-Agent: FACT tngweather\r\n"
247 "Accept: */*\r\n"
248 "Host: "+Address()+"\r\n"
249 "Connection: close\r\n"//Keep-Alive\r\n"
250 "Content-Type: application/rss+xml\r\n"
251 "User-Agent: FACT\r\n"
252 "Pragma: no-cache\r\n"
253 "Cache-Control: no-cache\r\n"
254 "Expires: 0\r\n"
255 "Cache-Control: max-age=0\r\n"
256 "\r\n";
257
258 PostMessage(cmd);
259 }
260
261 void Request()
262 {
263 PostRequest();
264
265 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval));
266 fKeepAlive.async_wait(boost::bind(&ConnectionWeather::HandleRequest,
267 this, dummy::error));
268 }
269
270 void HandleRequest(const bs::error_code &error)
271 {
272 // 125: Operation canceled (bs::error_code(125, bs::system_category))
273 if (error && error!=ba::error::basic_errors::operation_aborted)
274 {
275 ostringstream str;
276 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
277 Error(str);
278
279 PostClose(false);
280 return;
281 }
282
283 if (!is_open())
284 {
285 // For example: Here we could schedule a new accept if we
286 // would not want to allow two connections at the same time.
287 PostClose(true);
288 return;
289 }
290
291 // Check whether the deadline has passed. We compare the deadline
292 // against the current time since a new asynchronous operation
293 // may have moved the deadline before this actor had a chance
294 // to run.
295 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
296 return;
297
298 Request();
299 }
300
301
302private:
303 // This is called when a connection was established
304 void ConnectionEstablished()
305 {
306 Request();
307 StartReadReport();
308 }
309
310public:
311
312 static const uint16_t kMaxAddr;
313
314public:
315 ConnectionWeather(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
316 fIsVerbose(true), fDust(-1),
317 fLastReport(Time::none), fLastReception(Time::none),
318 fKeepAlive(ioservice)
319 {
320 SetLogStream(&imp);
321 }
322
323 void SetVerbose(bool b)
324 {
325 fIsVerbose = b;
326 Connection::SetVerbose(b);
327 }
328
329 void SetInterval(uint16_t i)
330 {
331 fInterval = i;
332 }
333
334 void SetSite(const string &site)
335 {
336 fSite = site;
337 }
338
339 int GetState() const
340 {
341 if (fLastReport.IsValid() && fLastReport+boost::posix_time::seconds(fInterval*2)>Time())
342 return 3; // receiving
343
344 if (fLastReception.IsValid() && fLastReception+boost::posix_time::seconds(fInterval*2)>Time())
345 return 2; // connected
346
347 return 1; // Disconnected
348 }
349};
350
351const uint16_t ConnectionWeather::kMaxAddr = 0xfff;
352
353// ------------------------------------------------------------------------
354
355#include "DimDescriptionService.h"
356
357class ConnectionDimWeather : public ConnectionWeather
358{
359private:
360 DimDescribedService fDimWeather;
361 DimDescribedService fDimAtmosphere;
362
363 virtual void UpdateWeather(const Time &t, const DimWeather &data)
364 {
365 fDimWeather.setData(&data, sizeof(DimWeather));
366 fDimWeather.Update(t);
367 }
368
369 virtual void UpdateDust(const Time &t, const float &dust)
370 {
371 fDimAtmosphere.setData(&dust, sizeof(float));
372 fDimAtmosphere.Update(t);
373 }
374
375public:
376 ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) :
377 ConnectionWeather(ioservice, imp),
378 fDimWeather("TNG_WEATHER/DATA", "F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1;D:1;F:1;F:1;F:1;F:1",
379 "|T[deg C]:Temperature"
380 "|T_dew[deg C]:Dew point"
381 "|H[%]:Humidity"
382 "|P[mbar]:Air pressure"
383 "|Trend"
384 "|v[km/h]:Wind speed"
385 "|d[deg]:Wind direction (N-E)"
386 "|Dust[ug/m^3]:Dust (total)"
387 "|SeeingDate[mjd]:Seeing MJD"
388 "|Seeing[arcsec]:Seeing"
389 "|Seeing[arcsec]:Seeing Median"
390 "|SeeingStdev[arcsec]:Seeing Stdev"
391 "|Solarimeter[W/m^2]:Solarimeter"),
392 fDimAtmosphere("TNG_WEATHER/DUST", "F:1",
393 "|Dust[ug/m^3]:Dust (total)")
394 {
395 }
396};
397
398// ------------------------------------------------------------------------
399
400template <class T, class S>
401class StateMachineWeather : public StateMachineAsio<T>
402{
403private:
404 S fWeather;
405
406 bool CheckEventSize(size_t has, const char *name, size_t size)
407 {
408 if (has==size)
409 return true;
410
411 ostringstream msg;
412 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
413 T::Fatal(msg);
414 return false;
415 }
416
417 int SetVerbosity(const EventImp &evt)
418 {
419 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
420 return T::kSM_FatalError;
421
422 fWeather.SetVerbose(evt.GetBool());
423
424 return T::GetCurrentState();
425 }
426/*
427 int Disconnect()
428 {
429 // Close all connections
430 fWeather.PostClose(false);
431
432 return T::GetCurrentState();
433 }
434
435 int Reconnect(const EventImp &evt)
436 {
437 // Close all connections to supress the warning in SetEndpoint
438 fWeather.PostClose(false);
439
440 // Now wait until all connection have been closed and
441 // all pending handlers have been processed
442 poll();
443
444 if (evt.GetBool())
445 fWeather.SetEndpoint(evt.GetString());
446
447 // Now we can reopen the connection
448 fWeather.PostClose(true);
449
450 return T::GetCurrentState();
451 }
452*/
453 int Execute()
454 {
455 return fWeather.GetState();
456 }
457
458
459public:
460 StateMachineWeather(ostream &out=cout) :
461 StateMachineAsio<T>(out, "TNG_WEATHER"), fWeather(*this, *this)
462 {
463 // State names
464 T::AddStateName(State::kDisconnected, "NoConnection",
465 "No connection to web-server could be established recently");
466
467 T::AddStateName(State::kConnected, "Invalid",
468 "Connection to webserver can be established, but received data is not recent or invalid");
469
470 T::AddStateName(State::kReceiving, "Valid",
471 "Connection to webserver can be established, receint data received");
472
473 // Verbosity commands
474 T::AddEvent("SET_VERBOSE", "B")
475 (bind(&StateMachineWeather::SetVerbosity, this, placeholders::_1))
476 ("set verbosity state"
477 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
478/*
479 // Conenction commands
480 AddEvent("DISCONNECT")
481 (bind(&StateMachineWeather::Disconnect, this))
482 ("disconnect from ethernet");
483
484 AddEvent("RECONNECT", "O")
485 (bind(&StateMachineWeather::Reconnect, this, placeholders::_1))
486 ("(Re)connect ethernet connection to FTM, a new address can be given"
487 "|[host][string]:new ethernet address in the form <host:port>");
488*/
489 }
490
491 int EvalOptions(Configuration &conf)
492 {
493 fWeather.SetVerbose(!conf.Get<bool>("quiet"));
494 fWeather.SetInterval(conf.Get<uint16_t>("interval"));
495 fWeather.SetDebugTx(conf.Get<bool>("debug-tx"));
496 fWeather.SetSite(conf.Get<string>("url"));
497 fWeather.SetEndpoint(conf.Get<string>("addr"));
498 fWeather.StartConnect();
499
500 return -1;
501 }
502};
503
504// ------------------------------------------------------------------------
505
506#include "Main.h"
507
508
509template<class T, class S, class R>
510int RunShell(Configuration &conf)
511{
512 return Main::execute<T, StateMachineWeather<S, R>>(conf);
513}
514
515void SetupConfiguration(Configuration &conf)
516{
517 po::options_description control("TNG weather control options");
518 control.add_options()
519 ("no-dim,d", po_switch(), "Disable dim services")
520 ("addr,a", var<string>("tngweb.tng.iac.es:80"), "Network address of Cosy")
521 ("url,u", var<string>("/api/meteo/weather/feed.xml"), "File name and path to load")
522 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
523 ("interval,i", var<uint16_t>(300), "Interval between two updates on the server in seconds")
524 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
525 ;
526
527 conf.AddOptions(control);
528}
529
530/*
531 Extract usage clause(s) [if any] for SYNOPSIS.
532 Translators: "Usage" and "or" here are patterns (regular expressions) which
533 are used to match the usage synopsis in program output. An example from cp
534 (GNU coreutils) which contains both strings:
535 Usage: cp [OPTION]... [-T] SOURCE DEST
536 or: cp [OPTION]... SOURCE... DIRECTORY
537 or: cp [OPTION]... -t DIRECTORY SOURCE...
538 */
539void PrintUsage()
540{
541 cout <<
542 "The tngweather is an interface to the TNG weather data.\n"
543 "\n"
544 "The default is that the program is started without user intercation. "
545 "All actions are supposed to arrive as DimCommands. Using the -c "
546 "option, a local shell can be initialized. With h or help a short "
547 "help message about the usuage can be brought to the screen.\n"
548 "\n"
549 "Usage: tngweather [-c type] [OPTIONS]\n"
550 " or: tngweather [OPTIONS]\n";
551 cout << endl;
552}
553
554void PrintHelp()
555{
556// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
557
558 /* Additional help text which is printed after the configuration
559 options goes here */
560
561 /*
562 cout << "bla bla bla" << endl << endl;
563 cout << endl;
564 cout << "Environment:" << endl;
565 cout << "environment" << endl;
566 cout << endl;
567 cout << "Examples:" << endl;
568 cout << "test exam" << endl;
569 cout << endl;
570 cout << "Files:" << endl;
571 cout << "files" << endl;
572 cout << endl;
573 */
574}
575
576int main(int argc, const char* argv[])
577{
578 Configuration conf(argv[0]);
579 conf.SetPrintUsage(PrintUsage);
580 Main::SetupConfiguration(conf);
581 SetupConfiguration(conf);
582
583 if (!conf.DoParse(argc, argv, PrintHelp))
584 return 127;
585
586 //try
587 {
588 // No console access at all
589 if (!conf.Has("console"))
590 {
591 if (conf.Get<bool>("no-dim"))
592 return RunShell<LocalStream, StateMachine, ConnectionWeather>(conf);
593 else
594 return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf);
595 }
596 // Cosole access w/ and w/o Dim
597 if (conf.Get<bool>("no-dim"))
598 {
599 if (conf.Get<int>("console")==0)
600 return RunShell<LocalShell, StateMachine, ConnectionWeather>(conf);
601 else
602 return RunShell<LocalConsole, StateMachine, ConnectionWeather>(conf);
603 }
604 else
605 {
606 if (conf.Get<int>("console")==0)
607 return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf);
608 else
609 return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf);
610 }
611 }
612 /*catch (std::exception& e)
613 {
614 cerr << "Exception: " << e.what() << endl;
615 return -1;
616 }*/
617
618 return 0;
619}
Note: See TracBrowser for help on using the repository browser.