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

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