source: trunk/FACT++/src/gtcdust.cc@ 19795

Last change on this file since 19795 was 19713, checked in by tbretz, 5 years ago
Forgot to rename the server.
File size: 14.4 KB
Line 
1#include <boost/array.hpp>
2#include <boost/property_tree/ptree.hpp>
3#include <boost/property_tree/json_parser.hpp>
4
5#include <string> // std::string
6#include <algorithm> // std::transform
7#include <cctype> // std::tolower
8
9#include "FACT.h"
10#include "Dim.h"
11#include "Event.h"
12#include "Shell.h"
13#include "StateMachineDim.h"
14#include "StateMachineAsio.h"
15#include "Connection.h"
16#include "LocalControl.h"
17#include "Configuration.h"
18#include "Console.h"
19
20#include "tools.h"
21
22#include "HeadersGTC.h"
23
24namespace ba = boost::asio;
25namespace bs = boost::system;
26namespace pt = boost::property_tree;
27namespace dummy = ba::placeholders;
28
29using namespace std;
30using namespace GTC;
31
32// ------------------------------------------------------------------------
33
34class ConnectionGTC : public Connection
35{
36 uint16_t fInterval;
37
38 bool fIsVerbose;
39
40 string fSite;
41
42protected:
43
44 Time fLastReport;
45 Time fLastReception;
46
47 boost::asio::streambuf fBuffer;
48 string fData;
49
50 virtual void UpdateGTC(const Time &t, const float &)
51 {
52 }
53
54 void ProcessAnswer(string s)
55 {
56 try
57 {
58 std::stringstream ss;
59 ss << s;
60
61 pt::ptree tree;
62 pt::read_json(ss, tree);
63
64 // {"pm25":{"Value":"13.9000","Date":"2019-10-03 10:31:40"}}
65
66 const auto &pm25 = tree.get_child("pm25");
67
68 const float value = pm25.get_child("Value").get_value<float>();
69
70 Time date;
71 date.SetFromStr(pm25.get_child("Date").get_value<string>());
72
73 if (date!=fLastReport)
74 {
75 Info(date.GetAsStr()+": "+Tools::Form("%.2f", value)+" ug/m^3");
76 UpdateGTC(date, value);
77 fLastReport = date;
78 }
79 }
80 catch (std::exception const& e)
81 {
82 Error(string("Parsing JSON failed: ")+e.what());
83 }
84 }
85
86 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
87 {
88 // Do not schedule a new read if the connection failed.
89 if (bytes_received==0 || err)
90 {
91 // The last report (payload) is simply termined by
92 // the connection being closed by the server.
93 // I do not understand why bytes_received is 0 then.
94 //
95 // Extract whatever is left in the buffer
96 istream is(&fBuffer);
97 string buffer;
98 getline(is, buffer);
99 fData += buffer;
100
101 if (fIsVerbose)
102 Out() << "EOF|" << buffer << endl;
103
104 if (err==ba::error::eof)
105 {
106 // Does the message contain a header?
107 const size_t p1 = fData.find("\r\n\r\n");
108 if (p1!=string::npos)
109 ProcessAnswer(fData.substr(p1));
110 else
111 Warn("Received message lacks a header!");
112 fData = "";
113
114 PostClose(false);
115
116 return;
117 }
118
119 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
120 // 125: Operation canceled
121 if (err && err!=ba::error::eof && // Connection closed by remote host
122 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
123 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
124 {
125 ostringstream str;
126 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
127 Error(str);
128 }
129 PostClose(err!=ba::error::basic_errors::operation_aborted);
130 return;
131 }
132
133 fLastReception = Time();
134
135 istream is(&fBuffer);
136
137 string buffer;
138 if (!getline(is, buffer, '\n'))
139 {
140 Fatal("Received message does not contain \\n... closing connection.");
141 PostClose(false);
142 return;
143 }
144
145 if (fIsVerbose)
146 Out() << bytes_received << "|" << buffer << endl;
147
148 fData += buffer;
149 fData += '\n';
150
151 StartReadLine();
152 }
153
154 void StartReadLine()
155 {
156 // The last report (payload) is simply termined by the connection being closed by the server.
157 async_read_until(*this, fBuffer, '\n',
158 boost::bind(&ConnectionGTC::HandleRead, this,
159 dummy::error, dummy::bytes_transferred));
160 }
161
162 ba::deadline_timer fKeepAlive;
163
164 bool fRequestPayload;
165
166 void PostRequest()
167 {
168 const string cmd =
169 "GET "+fSite+" HTTP/1.1\r\n"
170 "User-Agent: FACT gtcdust\r\n"
171 "Accept: */*\r\n"
172 "Accept-Encoding: identity\r\n"
173 "Host: "+URL()+"\r\n"
174 "Connection: close\r\n"
175 "Pragma: no-cache\r\n"
176 "Cache-Control: no-cache\r\n"
177 "Expires: 0\r\n"
178 "Cache-Control: max-age=0\r\n"
179 "\r\n";
180
181 PostMessage(cmd);
182 }
183
184 void Request()
185 {
186 PostRequest();
187
188 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval/2));
189 fKeepAlive.async_wait(boost::bind(&ConnectionGTC::HandleRequest,
190 this, dummy::error));
191 }
192
193 void HandleRequest(const bs::error_code &error)
194 {
195 // 125: Operation canceled (bs::error_code(125, bs::system_category))
196 if (error && error!=ba::error::basic_errors::operation_aborted)
197 {
198 ostringstream str;
199 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
200 Error(str);
201
202 PostClose(false);
203 return;
204 }
205
206 if (!is_open())
207 {
208 // For example: Here we could schedule a new accept if we
209 // would not want to allow two connections at the same time.
210 PostClose(true);
211 return;
212 }
213
214 // Check whether the deadline has passed. We compare the deadline
215 // against the current time since a new asynchronous operation
216 // may have moved the deadline before this actor had a chance
217 // to run.
218 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
219 return;
220
221 Request();
222 }
223
224
225private:
226 // This is called when a connection was established
227 void ConnectionEstablished()
228 {
229 Request();
230 StartReadLine();
231 }
232
233public:
234 ConnectionGTC(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
235 fIsVerbose(true), fLastReport(Time::none), fLastReception(Time::none), fKeepAlive(ioservice)
236 {
237 SetLogStream(&imp);
238 }
239
240 void SetVerbose(bool b)
241 {
242 fIsVerbose = b;
243 Connection::SetVerbose(b);
244 }
245
246 void SetInterval(uint16_t i)
247 {
248 fInterval = i;
249 }
250
251 void SetSite(const string &site)
252 {
253 fSite = site;
254 }
255
256 int GetState() const
257 {
258 if (fLastReport.IsValid() && fLastReport+boost::posix_time::seconds(fInterval*2)>Time())
259 return 3;
260
261 if (fLastReception.IsValid() && fLastReception+boost::posix_time::seconds(fInterval*2)>Time())
262 return 2;
263
264 return 1;
265
266 }
267};
268
269// ------------------------------------------------------------------------
270
271#include "DimDescriptionService.h"
272
273class ConnectionDimGTC : public ConnectionGTC
274{
275private:
276
277 DimDescribedService fDimGTC;
278
279 virtual void UpdateGTC(const Time &t, const float &data)
280 {
281 fDimGTC.setData(&data, sizeof(float));
282 fDimGTC.Update(t);
283 }
284
285public:
286 ConnectionDimGTC(ba::io_service& ioservice, MessageImp &imp) :
287 ConnectionGTC(ioservice, imp),
288 fDimGTC("GTC_DUST/DATA", "F:1",
289 "|dust[ug/m3]:Seconds since device start")
290 {
291 }
292};
293
294// ------------------------------------------------------------------------
295
296template <class T, class S>
297class StateMachineGTC : public StateMachineAsio<T>
298{
299private:
300 S fGTC;
301
302 bool CheckEventSize(size_t has, const char *name, size_t size)
303 {
304 if (has==size)
305 return true;
306
307 ostringstream msg;
308 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
309 T::Fatal(msg);
310 return false;
311 }
312
313 int SetVerbosity(const EventImp &evt)
314 {
315 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
316 return T::kSM_FatalError;
317
318 fGTC.SetVerbose(evt.GetBool());
319
320 return T::GetCurrentState();
321 }
322/*
323 int Disconnect()
324 {
325 // Close all connections
326 fBiasTemp.PostClose(false);
327
328 return T::GetCurrentState();
329 }
330
331 int Reconnect(const EventImp &evt)
332 {
333 // Close all connections to supress the warning in SetEndpoint
334 fBiasTemp.PostClose(false);
335
336 // Now wait until all connection have been closed and
337 // all pending handlers have been processed
338 ba::io_service::poll();
339
340 if (evt.GetBool())
341 fBiasTemp.SetEndpoint(evt.GetString());
342
343 // Now we can reopen the connection
344 fBiasTemp.PostClose(true);
345
346 return T::GetCurrentState();
347 }
348*/
349 int Execute()
350 {
351 return fGTC.GetState();
352 }
353
354public:
355 StateMachineGTC(ostream &out=cout) :
356 StateMachineAsio<T>(out, "GTC_DUST"), fGTC(*this, *this)
357 {
358 // State names
359 T::AddStateName(State::kDisconnected, "NoConnection",
360 "No connection to web-server could be established recently");
361
362 T::AddStateName(State::kConnected, "Invalid",
363 "Connection to webserver can be established, but received data is not recent or invalid");
364
365 T::AddStateName(State::kReceiving, "Valid",
366 "Connection to webserver can be established, receint data received");
367
368 // Verbosity commands
369 T::AddEvent("SET_VERBOSE", "B")
370 (bind(&StateMachineGTC::SetVerbosity, this, placeholders::_1))
371 ("set verbosity state"
372 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
373/*
374 // Conenction commands
375 AddEvent("DISCONNECT")
376 (bind(&StateMachineBiasTemp::Disconnect, this))
377 ("disconnect from ethernet");
378
379 AddEvent("RECONNECT", "O")
380 (bind(&StateMachineBiasTemp::Reconnect, this, placeholders::_1))
381 ("(Re)connect ethernet connection to FTM, a new address can be given"
382 "|[host][string]:new ethernet address in the form <host:port>");
383*/
384 }
385
386 int EvalOptions(Configuration &conf)
387 {
388 fGTC.SetVerbose(!conf.Get<bool>("quiet"));
389 fGTC.SetInterval(conf.Get<uint16_t>("interval"));
390 fGTC.SetDebugTx(conf.Get<bool>("debug-tx"));
391 fGTC.SetSite(conf.Get<string>("url"));
392 fGTC.SetEndpoint(conf.Get<string>("addr"));
393 fGTC.StartConnect();
394
395 return -1;
396 }
397};
398
399
400
401// ------------------------------------------------------------------------
402
403#include "Main.h"
404
405
406template<class T, class S, class R>
407int RunShell(Configuration &conf)
408{
409 return Main::execute<T, StateMachineGTC<S, R>>(conf);
410}
411
412void SetupConfiguration(Configuration &conf)
413{
414 po::options_description control("Options");
415 control.add_options()
416 ("no-dim,d", po_switch(), "Disable dim services")
417 ("addr,a", var<string>("atmosportal.gtc.iac.es:80"), "Network address of the hardware")
418 ("url,u", var<string>("/queries/pm25"), "File name and path to load")
419 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
420 ("interval,i", var<uint16_t>(120), "Interval between two data updates in second (request time is half, timeout is double)")
421 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
422 ;
423
424 conf.AddOptions(control);
425}
426
427/*
428 Extract usage clause(s) [if any] for SYNOPSIS.
429 Translators: "Usage" and "or" here are patterns (regular expressions) which
430 are used to match the usage synopsis in program output. An example from cp
431 (GNU coreutils) which contains both strings:
432 Usage: cp [OPTION]... [-T] SOURCE DEST
433 or: cp [OPTION]... SOURCE... DIRECTORY
434 or: cp [OPTION]... -t DIRECTORY SOURCE...
435 */
436void PrintUsage()
437{
438 cout <<
439 "The gtdust is an interface to the GTC dust measurement.\n"
440 "\n"
441 "The default is that the program is started without user intercation. "
442 "All actions are supposed to arrive as DimCommands. Using the -c "
443 "option, a local shell can be initialized. With h or help a short "
444 "help message about the usuage can be brought to the screen.\n"
445 "\n"
446 "Usage: biastemp [-c type] [OPTIONS]\n"
447 " or: biastemp [OPTIONS]\n";
448 cout << endl;
449}
450
451void PrintHelp()
452{
453// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
454
455 /* Additional help text which is printed after the configuration
456 options goes here */
457
458 /*
459 cout << "bla bla bla" << endl << endl;
460 cout << endl;
461 cout << "Environment:" << endl;
462 cout << "environment" << endl;
463 cout << endl;
464 cout << "Examples:" << endl;
465 cout << "test exam" << endl;
466 cout << endl;
467 cout << "Files:" << endl;
468 cout << "files" << endl;
469 cout << endl;
470 */
471}
472
473int main(int argc, const char* argv[])
474{
475 Configuration conf(argv[0]);
476 conf.SetPrintUsage(PrintUsage);
477 Main::SetupConfiguration(conf);
478 SetupConfiguration(conf);
479
480 if (!conf.DoParse(argc, argv, PrintHelp))
481 return 127;
482
483 //try
484 {
485 // No console access at all
486 if (!conf.Has("console"))
487 {
488 if (conf.Get<bool>("no-dim"))
489 return RunShell<LocalStream, StateMachine, ConnectionGTC>(conf);
490 else
491 return RunShell<LocalStream, StateMachineDim, ConnectionDimGTC>(conf);
492 }
493 // Cosole access w/ and w/o Dim
494 if (conf.Get<bool>("no-dim"))
495 {
496 if (conf.Get<int>("console")==0)
497 return RunShell<LocalShell, StateMachine, ConnectionGTC>(conf);
498 else
499 return RunShell<LocalConsole, StateMachine, ConnectionGTC>(conf);
500 }
501 else
502 {
503 if (conf.Get<int>("console")==0)
504 return RunShell<LocalShell, StateMachineDim, ConnectionDimGTC>(conf);
505 else
506 return RunShell<LocalConsole, StateMachineDim, ConnectionDimGTC>(conf);
507 }
508 }
509 /*catch (std::exception& e)
510 {
511 cerr << "Exception: " << e.what() << endl;
512 return -1;
513 }*/
514
515 return 0;
516}
Note: See TracBrowser for help on using the repository browser.