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

Last change on this file since 16991 was 16955, checked in by tbretz, 11 years ago
Removed the need for the Soprano library, fixed the units in the reports, added solarimeter measurements and updates the seeing to seeing median and stdev.
File size: 19.3 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 item = root.firstChildElement("item");
142
143 const QDomElement med = item.firstChildElement("tngw:dimmSeeing.median");
144 const QDomElement sdev = item.firstChildElement("tngw:dimmSeeing.stdev");
145 const QDomElement dust = item.firstChildElement("tngw:dustTotal");
146 const QDomElement delta = item.firstChildElement("tngw:deltaM1");
147 const QDomElement pres = item.firstChildElement("tngw:airPressure");
148 const QDomElement dew = item.firstChildElement("tngw:dewPoint");
149 const QDomElement wdir = item.firstChildElement("tngw:windDirection");
150 const QDomElement speed = item.firstChildElement("tngw:windSpeed");
151 const QDomElement hum = item.firstChildElement("tngw:hum");
152 const QDomElement tmp0 = item.firstChildElement("tngw:tempGround");
153 const QDomElement tmp2 = item.firstChildElement("tngw:temp2M");
154 const QDomElement tmp5 = item.firstChildElement("tngw:temp5M");
155 const QDomElement tmp10 = item.firstChildElement("tngw:temp10M");
156 const QDomElement solar = item.firstChildElement("tngw:solarimeter");
157 const QDomElement date = item.firstChildElement("tngw:date");
158
159 if (med.isNull() || sdev.isNull() || dust.isNull() || delta.isNull() ||
160 pres.isNull() || dew.isNull() || wdir.isNull() || speed.isNull() ||
161 hum.isNull() || hum.isNull() || tmp0.isNull() || tmp2.isNull() ||
162 tmp5.isNull() || tmp10.isNull() || solar.isNull() || date.isNull())
163 {
164 Warn("Parsing of xml failed [1].");
165 PostClose(false);
166 return;
167 }
168
169 DimWeather w;
170 w.fSeeingMed = med .text().toFloat();
171 w.fSeeingStdev = sdev .text().toFloat();
172 w.fDustTotal = dust .text().toFloat();
173 w.fDeltaM1 = delta.text().toFloat();
174 w.fAirPressure = pres .text().toFloat();
175 w.fDewPoint = dew .text().toFloat();
176 w.fWindDirection = wdir .text().toFloat();
177 w.fWindSpeed = speed.text().toFloat()*3.6;
178 w.fHumidity = hum .text().toFloat();
179 w.fTempGround = tmp0 .text().toFloat();
180 w.fTemp2M = tmp2 .text().toFloat();
181 w.fTemp5M = tmp5 .text().toFloat();
182 w.fTemp10M = tmp10.text().toFloat();
183 w.fSolarimeter = solar.text().toFloat();
184
185 const string obj = date.text().toStdString();
186
187 Time time(obj);
188 if (!time.IsValid())
189 {
190 struct tm tm;
191
192 vector<char> buf(255);
193 if (strptime(obj.c_str(), "%c", &tm))
194 time = Time(tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
195 tm.tm_hour, tm.tm_min, tm.tm_sec);
196 }
197
198 if (!time.IsValid())
199 throw runtime_error("time invalid");
200
201 if (time!=fLastReport && fIsVerbose)
202 {
203 Out() << endl;
204 Out() << "Date: " << time << endl;
205 Out() << "Seeing: " << w.fSeeingMed << " +- " << w.fSeeingStdev << endl;
206 Out() << "DustTotal: " << w.fDustTotal << endl;
207 Out() << "DeltaM1: " << w.fDeltaM1 << endl;
208 Out() << "AirPressure: " << w.fAirPressure << 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() << "TempGround: " << w.fTempGround << endl;
214 Out() << "Temp2M: " << w.fTemp2M << endl;
215 Out() << "Temp5M: " << w.fTemp5M << endl;
216 Out() << "Temp10M: " << w.fTemp10M << endl;
217 Out() << "Solarimeter: " << w.fSolarimeter << endl;
218 Out() << endl;
219 }
220
221 fLastReport = time;
222
223 UpdateWeather(time, w);
224
225 if (fDust==w.fDustTotal)
226 return;
227
228 UpdateDust(time, w.fDustTotal);
229 fDust = w.fDustTotal;
230
231 ostringstream out;
232 out << setprecision(3) << "Dust: " << fDust << "ug/m^3 [" << time << "]";
233 Message(out);
234 }
235
236 void StartReadReport()
237 {
238 async_read_some(ba::buffer(fArray),
239 boost::bind(&ConnectionWeather::HandleRead, this,
240 dummy::error, dummy::bytes_transferred));
241 }
242
243 boost::asio::deadline_timer fKeepAlive;
244
245 void PostRequest()
246 {
247 const string cmd =
248 "GET "+fSite+" HTTP/1.1\r\n"
249 "Accept: */*\r\n"
250 "Content-Type: application/octet-stream\r\n"
251 "User-Agent: FACT\r\n"
252 "Host: www.fact-project.org\r\n"
253 "Pragma: no-cache\r\n"
254 "Cache-Control: no-cache\r\n"
255 "Expires: 0\r\n"
256 "Connection: Keep-Alive\r\n"
257 "Cache-Control: max-age=0\r\n"
258 "\r\n";
259
260 PostMessage(cmd);
261 }
262
263 void Request()
264 {
265 PostRequest();
266
267 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval));
268 fKeepAlive.async_wait(boost::bind(&ConnectionWeather::HandleRequest,
269 this, dummy::error));
270 }
271
272 void HandleRequest(const bs::error_code &error)
273 {
274 // 125: Operation canceled (bs::error_code(125, bs::system_category))
275 if (error && error!=ba::error::basic_errors::operation_aborted)
276 {
277 ostringstream str;
278 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
279 Error(str);
280
281 PostClose(false);
282 return;
283 }
284
285 if (!is_open())
286 {
287 // For example: Here we could schedule a new accept if we
288 // would not want to allow two connections at the same time.
289 PostClose(true);
290 return;
291 }
292
293 // Check whether the deadline has passed. We compare the deadline
294 // against the current time since a new asynchronous operation
295 // may have moved the deadline before this actor had a chance
296 // to run.
297 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
298 return;
299
300 Request();
301 }
302
303
304private:
305 // This is called when a connection was established
306 void ConnectionEstablished()
307 {
308 Request();
309 StartReadReport();
310 }
311
312public:
313
314 static const uint16_t kMaxAddr;
315
316public:
317 ConnectionWeather(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
318 fIsVerbose(true), fDust(-1),
319 fLastReport(Time::none), fLastReception(Time::none),
320 fKeepAlive(ioservice)
321 {
322 SetLogStream(&imp);
323 }
324
325 void SetVerbose(bool b)
326 {
327 fIsVerbose = b;
328 Connection::SetVerbose(b);
329 }
330
331 void SetInterval(uint16_t i)
332 {
333 fInterval = i;
334 }
335
336 void SetSite(const string &site)
337 {
338 fSite = site;
339 }
340
341 int GetState() const
342 {
343 if (fLastReport.IsValid() && fLastReport+boost::posix_time::seconds(fInterval*2)>Time())
344 return 3; // receiving
345
346 if (fLastReception.IsValid() && fLastReception+boost::posix_time::seconds(fInterval*2)>Time())
347 return 2; // connected
348
349 return 1; // Disconnected
350 }
351};
352
353const uint16_t ConnectionWeather::kMaxAddr = 0xfff;
354
355// ------------------------------------------------------------------------
356
357#include "DimDescriptionService.h"
358
359class ConnectionDimWeather : public ConnectionWeather
360{
361private:
362 DimDescribedService fDimWeather;
363 DimDescribedService fDimAtmosphere;
364
365 virtual void UpdateWeather(const Time &t, const DimWeather &data)
366 {
367 fDimWeather.setData(&data, sizeof(DimWeather));
368 fDimWeather.Update(t);
369 }
370
371 virtual void UpdateDust(const Time &t, const float &dust)
372 {
373 fDimAtmosphere.setData(&dust, sizeof(float));
374 fDimAtmosphere.Update(t);
375 }
376
377public:
378 ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) :
379 ConnectionWeather(ioservice, imp),
380 fDimWeather("TNG_WEATHER/DATA", "F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1",
381 "|T_10M[deg C]:Temperature 10m above ground"
382 "|T_5M[deg C]:Temperature 5m above ground"
383 "|T_2M[deg C]:Temperature 2m above ground"
384 "|T_0[deg C]:Temperature at ground"
385 "|T_dew[deg C]:Dew point"
386 "|H[%]:Humidity"
387 "|P[mbar]:Air pressure"
388 "|v[km/h]:Wind speed"
389 "|d[deg]:Wind direction (N-E)"
390 "|DeltaM1"
391 "|Dust[ug/m^3]:Dust (total)"
392 "|Seeing[arcsec]:Seeing Median"
393 "|SeeingStdev[arcsec]:Seeing Stdev"
394 "|Solarimeter[W/m^2]:Solarimeter"),
395 fDimAtmosphere("TNG_WEATHER/DUST", "F:1",
396 "|Dust[ug/m^3]:Dust (total)")
397 {
398 }
399};
400
401// ------------------------------------------------------------------------
402
403template <class T, class S>
404class StateMachineWeather : public StateMachineAsio<T>
405{
406private:
407 S fWeather;
408
409 bool CheckEventSize(size_t has, const char *name, size_t size)
410 {
411 if (has==size)
412 return true;
413
414 ostringstream msg;
415 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
416 T::Fatal(msg);
417 return false;
418 }
419
420 int SetVerbosity(const EventImp &evt)
421 {
422 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
423 return T::kSM_FatalError;
424
425 fWeather.SetVerbose(evt.GetBool());
426
427 return T::GetCurrentState();
428 }
429/*
430 int Disconnect()
431 {
432 // Close all connections
433 fWeather.PostClose(false);
434
435 return T::GetCurrentState();
436 }
437
438 int Reconnect(const EventImp &evt)
439 {
440 // Close all connections to supress the warning in SetEndpoint
441 fWeather.PostClose(false);
442
443 // Now wait until all connection have been closed and
444 // all pending handlers have been processed
445 poll();
446
447 if (evt.GetBool())
448 fWeather.SetEndpoint(evt.GetString());
449
450 // Now we can reopen the connection
451 fWeather.PostClose(true);
452
453 return T::GetCurrentState();
454 }
455*/
456 int Execute()
457 {
458 return fWeather.GetState();
459 }
460
461
462public:
463 StateMachineWeather(ostream &out=cout) :
464 StateMachineAsio<T>(out, "TNG_WEATHER"), fWeather(*this, *this)
465 {
466 // State names
467 T::AddStateName(State::kDisconnected, "NoConnection",
468 "No connection to web-server could be established recently");
469
470 T::AddStateName(State::kConnected, "Invalid",
471 "Connection to webserver can be established, but received data is not recent or invalid");
472
473 T::AddStateName(State::kReceiving, "Valid",
474 "Connection to webserver can be established, receint data received");
475
476 // Verbosity commands
477 T::AddEvent("SET_VERBOSE", "B")
478 (bind(&StateMachineWeather::SetVerbosity, this, placeholders::_1))
479 ("set verbosity state"
480 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
481/*
482 // Conenction commands
483 AddEvent("DISCONNECT")
484 (bind(&StateMachineWeather::Disconnect, this))
485 ("disconnect from ethernet");
486
487 AddEvent("RECONNECT", "O")
488 (bind(&StateMachineWeather::Reconnect, this, placeholders::_1))
489 ("(Re)connect ethernet connection to FTM, a new address can be given"
490 "|[host][string]:new ethernet address in the form <host:port>");
491*/
492 }
493
494 int EvalOptions(Configuration &conf)
495 {
496 fWeather.SetVerbose(!conf.Get<bool>("quiet"));
497 fWeather.SetInterval(conf.Get<uint16_t>("interval"));
498 fWeather.SetDebugTx(conf.Get<bool>("debug-tx"));
499 fWeather.SetSite(conf.Get<string>("url"));
500 fWeather.SetEndpoint(conf.Get<string>("addr"));
501 fWeather.StartConnect();
502
503 return -1;
504 }
505};
506
507// ------------------------------------------------------------------------
508
509#include "Main.h"
510
511
512template<class T, class S, class R>
513int RunShell(Configuration &conf)
514{
515 return Main::execute<T, StateMachineWeather<S, R>>(conf);
516}
517
518void SetupConfiguration(Configuration &conf)
519{
520 po::options_description control("TNG weather control options");
521 control.add_options()
522 ("no-dim,d", po_switch(), "Disable dim services")
523 ("addr,a", var<string>("tngweb.tng.iac.es:80"), "Network address of Cosy")
524 ("url,u", var<string>("/weather/rss/"), "File name and path to load")
525 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
526 ("interval,i", var<uint16_t>(300), "Interval between two updates on the server in seconds")
527 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
528 ;
529
530 conf.AddOptions(control);
531}
532
533/*
534 Extract usage clause(s) [if any] for SYNOPSIS.
535 Translators: "Usage" and "or" here are patterns (regular expressions) which
536 are used to match the usage synopsis in program output. An example from cp
537 (GNU coreutils) which contains both strings:
538 Usage: cp [OPTION]... [-T] SOURCE DEST
539 or: cp [OPTION]... SOURCE... DIRECTORY
540 or: cp [OPTION]... -t DIRECTORY SOURCE...
541 */
542void PrintUsage()
543{
544 cout <<
545 "The tngweather is an interface to the TNG weather data.\n"
546 "\n"
547 "The default is that the program is started without user intercation. "
548 "All actions are supposed to arrive as DimCommands. Using the -c "
549 "option, a local shell can be initialized. With h or help a short "
550 "help message about the usuage can be brought to the screen.\n"
551 "\n"
552 "Usage: tngweather [-c type] [OPTIONS]\n"
553 " or: tngweather [OPTIONS]\n";
554 cout << endl;
555}
556
557void PrintHelp()
558{
559// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
560
561 /* Additional help text which is printed after the configuration
562 options goes here */
563
564 /*
565 cout << "bla bla bla" << endl << endl;
566 cout << endl;
567 cout << "Environment:" << endl;
568 cout << "environment" << endl;
569 cout << endl;
570 cout << "Examples:" << endl;
571 cout << "test exam" << endl;
572 cout << endl;
573 cout << "Files:" << endl;
574 cout << "files" << endl;
575 cout << endl;
576 */
577}
578
579int main(int argc, const char* argv[])
580{
581 Configuration conf(argv[0]);
582 conf.SetPrintUsage(PrintUsage);
583 Main::SetupConfiguration(conf);
584 SetupConfiguration(conf);
585
586 if (!conf.DoParse(argc, argv, PrintHelp))
587 return 127;
588
589 //try
590 {
591 // No console access at all
592 if (!conf.Has("console"))
593 {
594 if (conf.Get<bool>("no-dim"))
595 return RunShell<LocalStream, StateMachine, ConnectionWeather>(conf);
596 else
597 return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf);
598 }
599 // Cosole access w/ and w/o Dim
600 if (conf.Get<bool>("no-dim"))
601 {
602 if (conf.Get<int>("console")==0)
603 return RunShell<LocalShell, StateMachine, ConnectionWeather>(conf);
604 else
605 return RunShell<LocalConsole, StateMachine, ConnectionWeather>(conf);
606 }
607 else
608 {
609 if (conf.Get<int>("console")==0)
610 return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf);
611 else
612 return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf);
613 }
614 }
615 /*catch (std::exception& e)
616 {
617 cerr << "Exception: " << e.what() << endl;
618 return -1;
619 }*/
620
621 return 0;
622}
Note: See TracBrowser for help on using the repository browser.