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

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