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

Last change on this file since 20071 was 20017, checked in by tbretz, 4 years ago
Made ready for boost 1.70 by replacing get_io_context; the SSL connection has to be properly shutdown, but this required a new socket.. therefore switched to boost::optional to keep the socket.
File size: 19.6 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 stream->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 "Pragma: no-cache\r\n"
280 "Cache-Control: no-cache\r\n"
281 "Expires: 0\r\n"
282 "Cache-Control: max-age=0\r\n"
283 "\r\n";
284
285 PostMessage(cmd);
286 }
287
288 void Request()
289 {
290 PostRequest();
291
292 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval));
293 fKeepAlive.async_wait(boost::bind(&ConnectionWeather::HandleRequest,
294 this, dummy::error));
295 }
296
297 void HandleRequest(const bs::error_code &error)
298 {
299 // 125: Operation canceled (bs::error_code(125, bs::system_category))
300 if (error && error!=ba::error::basic_errors::operation_aborted)
301 {
302 ostringstream str;
303 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
304 Error(str);
305
306 PostClose(false);
307 return;
308 }
309
310 if (IsClosed())
311 {
312 // For example: Here we could schedule a new accept if we
313 // would not want to allow two connections at the same time.
314 PostClose(true);
315 return;
316 }
317
318 // Check whether the deadline has passed. We compare the deadline
319 // against the current time since a new asynchronous operation
320 // may have moved the deadline before this actor had a chance
321 // to run.
322 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
323 return;
324
325 Request();
326 }
327
328
329private:
330 // This is called when a connection was established
331 void ConnectionEstablished()
332 {
333 Request();
334 StartReadReport();
335 }
336
337public:
338
339 static const uint16_t kMaxAddr;
340
341public:
342 ConnectionWeather(ba::io_service& ioservice, MessageImp &imp) : ConnectionSSL(ioservice, imp()),
343 fIsVerbose(true), fDust(-1),
344 fLastReport(Time::none), fLastReception(Time::none), fLastSeeing(Time::none),
345 fKeepAlive(ioservice)
346 {
347 SetLogStream(&imp);
348 }
349
350 void SetVerbose(bool b)
351 {
352 fIsVerbose = b;
353 ConnectionSSL::SetVerbose(b);
354 }
355
356 void SetInterval(uint16_t i)
357 {
358 fInterval = i;
359 }
360
361 void SetSite(const string &site)
362 {
363 fSite = site;
364 }
365
366 int GetState() const
367 {
368 if (fLastReport.IsValid() && fLastReport+boost::posix_time::seconds(fInterval*2)>Time())
369 return 3; // receiving
370
371 if (fLastReception.IsValid() && fLastReception+boost::posix_time::seconds(fInterval*2)>Time())
372 return 2; // connected
373
374 return 1; // Disconnected
375 }
376};
377
378const uint16_t ConnectionWeather::kMaxAddr = 0xfff;
379
380// ------------------------------------------------------------------------
381
382#include "DimDescriptionService.h"
383
384class ConnectionDimWeather : public ConnectionWeather
385{
386private:
387 DimDescribedService fDimWeather;
388 DimDescribedService fDimAtmosphere;
389 DimDescribedService fDimSeeing;
390
391 virtual void UpdateWeather(const Time &t, const DimWeather &data)
392 {
393 fDimWeather.setData(&data, sizeof(DimWeather));
394 fDimWeather.Update(t);
395 }
396
397 virtual void UpdateDust(const Time &t, const float &dust)
398 {
399 fDimAtmosphere.setData(&dust, sizeof(float));
400 fDimAtmosphere.Update(t);
401 }
402
403 virtual void UpdateSeeing(const Time &t, const DimSeeing &see)
404 {
405 fDimSeeing.setData(&see, sizeof(DimSeeing));
406 fDimSeeing.Update(t);
407 }
408
409public:
410 ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) :
411 ConnectionWeather(ioservice, imp),
412 fDimWeather("TNG_WEATHER/DATA", "F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1",
413 "|T[deg C]:Temperature"
414 "|DeltaT[deg C]:Temperature trend 24h"
415 "|T_dew[deg C]:Dew point"
416 "|H[%]:Humidity"
417 "|P[mbar]:Air pressure"
418 "|v[km/h]:Wind speed"
419 "|d[deg]:Wind direction (N-E)"
420 "|Dust[ug/m^3]:Dust (total)"
421 "|Solarimeter[W/m^2]:Solarimeter"),
422 fDimAtmosphere("TNG_WEATHER/DUST", "F:1",
423 "|Dust[ug/m^3]:Dust (total)"),
424 fDimSeeing("TNG_WEATHER/SEEING", "F:1;F:1;F:1",
425 "|Seeing[arcsec]:Seeing"
426 "|SeeingMed[arcsec]:Seeing Median"
427 "|SeeingStdev[arcsec]:Seeing Stdev")
428 {
429 }
430};
431
432// ------------------------------------------------------------------------
433
434template <class T, class S>
435class StateMachineWeather : public StateMachineAsio<T>
436{
437private:
438 S fWeather;
439
440 bool CheckEventSize(size_t has, const char *name, size_t size)
441 {
442 if (has==size)
443 return true;
444
445 ostringstream msg;
446 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
447 T::Fatal(msg);
448 return false;
449 }
450
451 int SetVerbosity(const EventImp &evt)
452 {
453 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
454 return T::kSM_FatalError;
455
456 fWeather.SetVerbose(evt.GetBool());
457
458 return T::GetCurrentState();
459 }
460/*
461 int Disconnect()
462 {
463 // Close all connections
464 fWeather.PostClose(false);
465
466 return T::GetCurrentState();
467 }
468*/
469 int Connect()
470 {
471 fWeather.PostClose(true);
472 return T::GetCurrentState();
473 }
474
475 int Execute()
476 {
477 return fWeather.GetState();
478 }
479
480
481public:
482 StateMachineWeather(ostream &out=cout) :
483 StateMachineAsio<T>(out, "TNG_WEATHER"), fWeather(*this, *this)
484 {
485 // State names
486 T::AddStateName(State::kDisconnected, "NoConnection",
487 "No connection to web-server could be established recently");
488
489 T::AddStateName(State::kConnected, "Invalid",
490 "Connection to webserver can be established, but received data is not recent or invalid");
491
492 T::AddStateName(State::kReceiving, "Valid",
493 "Connection to webserver can be established, receint data received");
494
495 // Verbosity commands
496 T::AddEvent("SET_VERBOSE", "B")
497 (bind(&StateMachineWeather::SetVerbosity, this, placeholders::_1))
498 ("set verbosity state"
499 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
500/*
501 // Conenction commands
502 AddEvent("DISCONNECT")
503 (bind(&StateMachineWeather::Disconnect, this))
504 ("disconnect from ethernet");
505*/
506 T::AddEvent("CONNECT", "", State::kDisconnected)
507 (bind(&StateMachineWeather::Connect, this))
508 ("Connect");
509
510 }
511
512 int EvalOptions(Configuration &conf)
513 {
514 fWeather.SetVerbose(!conf.Get<bool>("quiet"));
515 fWeather.SetInterval(conf.Get<uint16_t>("interval"));
516 fWeather.SetDebugTx(conf.Get<bool>("debug-tx"));
517 fWeather.SetSite(conf.Get<string>("url"));
518 fWeather.SetEndpoint(conf.Get<string>("addr"));
519 fWeather.StartConnect();
520
521 return -1;
522 }
523};
524
525// ------------------------------------------------------------------------
526
527#include "Main.h"
528
529
530template<class T, class S, class R>
531int RunShell(Configuration &conf)
532{
533 return Main::execute<T, StateMachineWeather<S, R>>(conf);
534}
535
536void SetupConfiguration(Configuration &conf)
537{
538 po::options_description control("TNG weather control options");
539 control.add_options()
540 ("no-dim,d", po_switch(), "Disable dim services")
541 ("addr,a", var<string>("tngweb.tng.iac.es:443"), "Network address of Cosy")
542 ("url,u", var<string>("/api/meteo/weather/feed.xml"), "File name and path to load")
543 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
544 ("interval,i", var<uint16_t>(300), "Interval between two updates on the server in seconds")
545 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
546 ;
547
548 conf.AddOptions(control);
549}
550
551/*
552 Extract usage clause(s) [if any] for SYNOPSIS.
553 Translators: "Usage" and "or" here are patterns (regular expressions) which
554 are used to match the usage synopsis in program output. An example from cp
555 (GNU coreutils) which contains both strings:
556 Usage: cp [OPTION]... [-T] SOURCE DEST
557 or: cp [OPTION]... SOURCE... DIRECTORY
558 or: cp [OPTION]... -t DIRECTORY SOURCE...
559 */
560void PrintUsage()
561{
562 cout <<
563 "The tngweather is an interface to the TNG weather data.\n"
564 "\n"
565 "The default is that the program is started without user intercation. "
566 "All actions are supposed to arrive as DimCommands. Using the -c "
567 "option, a local shell can be initialized. With h or help a short "
568 "help message about the usuage can be brought to the screen.\n"
569 "\n"
570 "Usage: tngweather [-c type] [OPTIONS]\n"
571 " or: tngweather [OPTIONS]\n";
572 cout << endl;
573}
574
575void PrintHelp()
576{
577// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
578
579 /* Additional help text which is printed after the configuration
580 options goes here */
581
582 /*
583 cout << "bla bla bla" << endl << endl;
584 cout << endl;
585 cout << "Environment:" << endl;
586 cout << "environment" << endl;
587 cout << endl;
588 cout << "Examples:" << endl;
589 cout << "test exam" << endl;
590 cout << endl;
591 cout << "Files:" << endl;
592 cout << "files" << endl;
593 cout << endl;
594 */
595}
596
597int main(int argc, const char* argv[])
598{
599 Configuration conf(argv[0]);
600 conf.SetPrintUsage(PrintUsage);
601 Main::SetupConfiguration(conf);
602 SetupConfiguration(conf);
603
604 if (!conf.DoParse(argc, argv, PrintHelp))
605 return 127;
606
607 //try
608 {
609 // No console access at all
610 if (!conf.Has("console"))
611 {
612 if (conf.Get<bool>("no-dim"))
613 return RunShell<LocalStream, StateMachine, ConnectionWeather>(conf);
614 else
615 return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf);
616 }
617 // Cosole access w/ and w/o Dim
618 if (conf.Get<bool>("no-dim"))
619 {
620 if (conf.Get<int>("console")==0)
621 return RunShell<LocalShell, StateMachine, ConnectionWeather>(conf);
622 else
623 return RunShell<LocalConsole, StateMachine, ConnectionWeather>(conf);
624 }
625 else
626 {
627 if (conf.Get<int>("console")==0)
628 return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf);
629 else
630 return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf);
631 }
632 }
633 /*catch (std::exception& e)
634 {
635 cerr << "Exception: " << e.what() << endl;
636 return -1;
637 }*/
638
639 return 0;
640}
Note: See TracBrowser for help on using the repository browser.