source: trunk/FACT++/src/rainsensor.cc@ 19008

Last change on this file since 19008 was 18995, checked in by tbretz, 7 years ago
Allow to run without authentication.
File size: 16.3 KB
Line 
1#if BOOST_VERSION < 104600
2#include <assert.h>
3#endif
4
5#include <boost/array.hpp>
6
7#include <boost/property_tree/ptree.hpp>
8#include <boost/property_tree/json_parser.hpp>
9
10#include <boost/archive/iterators/base64_from_binary.hpp>
11#include <boost/archive/iterators/transform_width.hpp>
12
13#include <string> // std::string
14#include <algorithm> // std::transform
15#include <cctype> // std::tolower
16
17#include "FACT.h"
18#include "Dim.h"
19#include "Event.h"
20#include "Shell.h"
21#include "StateMachineDim.h"
22#include "StateMachineAsio.h"
23#include "Connection.h"
24#include "LocalControl.h"
25#include "Configuration.h"
26#include "Timers.h"
27#include "Console.h"
28
29#include "tools.h"
30
31#include "HeadersRainSensor.h"
32
33namespace ba = boost::asio;
34namespace bs = boost::system;
35namespace pt = boost::property_tree;
36namespace dummy = ba::placeholders;
37
38using namespace std;
39using namespace RainSensor;
40
41// ------------------------------------------------------------------------
42
43class ConnectionRain : public Connection
44{
45 uint16_t fInterval;
46
47 bool fDebugRx;
48
49 string fSite;
50 string fRdfData;
51 string fAuthentication;
52
53 boost::array<char, 4096> fArray;
54
55 Time fLastReport;
56
57 int fStatus;
58
59protected:
60 struct data_t
61 {
62 float rain;
63 int64_t stat;
64 } __attribute__((__packed__));
65
66 virtual void UpdateRain(const Time &, const data_t &)
67 {
68 }
69
70 void ProcessAnswer()
71 {
72 if (fDebugRx)
73 {
74 Out() << "------------------------------------------------------" << endl;
75 Out() << fRdfData << endl;
76 Out() << "------------------------------------------------------" << endl;
77 }
78
79 const size_t p1 = fRdfData.find("\r\n\r\n");
80 if (p1==string::npos)
81 {
82 Warn("HTTP header not found.");
83 PostClose(false);
84 return;
85 }
86
87 fRdfData.erase(0, p1+4);
88
89 data_t data;
90 Time time;
91 try
92 {
93 //{"rain": 0.0, "time": "2018-04-07T15:28:01.044273", "statistics": 30}
94
95 std::stringstream ss;
96 ss << fRdfData;
97
98 pt::ptree tree;
99 pt::read_json(ss, tree);
100
101 data.rain = tree.get_child("rain").get_value<float>();
102 data.stat = tree.get_child("statistics").get_value<int64_t>();
103 time = Time(tree.get_child("time").get_value<string>());
104 }
105 catch (std::exception const& e)
106 {
107 Warn("Parsing of JSON failed: "+string(e.what()));
108
109 fStatus = RainSensor::State::kConnected;
110
111 PostClose(false);
112 return;
113 }
114
115 fRdfData = "";
116
117 UpdateRain(time, data);
118
119 ostringstream msg;
120 msg << Time::iso << time << " - Rain=" << data.rain << " [N=" << data.stat << "]";
121 Message(msg);
122
123 fStatus = RainSensor::State::kValid;
124
125 fLastReport = Time();
126 PostClose(false);
127 }
128
129 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
130 {
131 // Do not schedule a new read if the connection failed.
132 if (bytes_received==0 || err)
133 {
134 if (err==ba::error::eof)
135 {
136 if (!fRdfData.empty())
137 ProcessAnswer();
138 return;
139 }
140
141 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
142 // 125: Operation canceled
143 if (err && err!=ba::error::eof && // Connection closed by remote host
144 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
145 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
146 {
147 ostringstream str;
148 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
149 Error(str);
150 }
151 PostClose(err!=ba::error::basic_errors::operation_aborted);
152
153 fRdfData = "";
154 return;
155 }
156
157 fRdfData += string(fArray.data(), bytes_received);
158
159 // Does the message contain a header?
160 const size_t p1 = fRdfData.find("\r\n\r\n");
161 if (p1!=string::npos)
162 {
163 // Does the answer also contain the body?
164 const size_t p2 = fRdfData.find("\r\n\r\n", p1+4);
165 if (p2!=string::npos)
166 ProcessAnswer();
167 }
168
169 // Go on reading until the web-server closes the connection
170 StartReadReport();
171 }
172
173 boost::asio::streambuf fBuffer;
174
175 void StartReadReport()
176 {
177 async_read_some(ba::buffer(fArray),
178 boost::bind(&ConnectionRain::HandleRead, this,
179 dummy::error, dummy::bytes_transferred));
180 }
181
182
183 ba::deadline_timer fKeepAlive;
184
185 void PostRequest()
186 {
187 const string auth = fAuthentication.empty() ? "" :
188 "Authorization: Basic "+fAuthentication+"\r\n";
189
190 const string cmd =
191 "GET "+fSite+" HTTP/1.1\r\n"
192 "Accept: */*\r\n"
193 "Content-Type: application/octet-stream\r\n"
194 +auth+
195 "User-Agent: FACT\r\n"
196 "Host: www.fact-project.org\r\n"
197 "Pragma: no-cache\r\n"
198 "Cache-Control: no-cache\r\n"
199 "Expires: 0\r\n"
200 "Connection: Keep-Alive\r\n"
201 "Cache-Control: max-age=0\r\n"
202 "\r\n";
203
204 PostMessage(cmd);
205 }
206
207 void Request()
208 {
209 PostRequest();
210
211 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval/2));
212 fKeepAlive.async_wait(boost::bind(&ConnectionRain::HandleRequest,
213 this, dummy::error));
214 }
215
216 void HandleRequest(const bs::error_code &error)
217 {
218 // 125: Operation canceled (bs::error_code(125, bs::system_category))
219 if (error && error!=ba::error::basic_errors::operation_aborted)
220 {
221 ostringstream str;
222 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
223 Error(str);
224
225 PostClose(false);
226 return;
227 }
228
229 if (!is_open())
230 {
231 // For example: Here we could schedule a new accept if we
232 // would not want to allow two connections at the same time.
233 PostClose(true);
234 return;
235 }
236
237 // Check whether the deadline has passed. We compare the deadline
238 // against the current time since a new asynchronous operation
239 // may have moved the deadline before this actor had a chance
240 // to run.
241 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
242 return;
243
244 Request();
245 }
246
247
248private:
249 // This is called when a connection was established
250 void ConnectionEstablished()
251 {
252 Request();
253 StartReadReport();
254 }
255
256public:
257
258 static const uint16_t kMaxAddr;
259
260public:
261 ConnectionRain(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
262 fLastReport(Time::none), fKeepAlive(ioservice)
263 {
264 SetLogStream(&imp);
265 }
266
267 void SetDebugRx(bool b)
268 {
269 fDebugRx = b;
270 }
271
272 void SetInterval(uint16_t i)
273 {
274 fInterval = i;
275 }
276
277 void SetSite(const string &site)
278 {
279 fSite = site;
280 }
281
282 int GetState() const
283 {
284 if (!fLastReport.IsValid() || Time()>fLastReport+boost::posix_time::seconds(fInterval*3))
285 return RainSensor::State::kDisconnected;
286
287 return fStatus;
288 }
289
290 void SetAuthentication(const string &user, const string &password)
291 {
292 if (user.empty() && password.empty())
293 {
294 fAuthentication = "";
295 return;
296 }
297
298 const string auth = user+":"+password;
299
300 // convert binary values to base64 characters
301 // retrieve 6 bit integers from a sequence of 8 bit bytes
302 // compose all the above operations in to a new iterator
303
304 using namespace boost::archive::iterators;
305 using it = base64_from_binary<transform_width<string::const_iterator, 6, 8>>;
306
307 fAuthentication = string(it(begin(auth)), it(end(auth)));
308 }
309};
310
311const uint16_t ConnectionRain::kMaxAddr = 0xfff;
312
313// ------------------------------------------------------------------------
314
315#include "DimDescriptionService.h"
316
317class ConnectionDimRain : public ConnectionRain
318{
319private:
320
321 DimDescribedService fDimRain;
322
323 virtual void UpdateRain(const Time &tm, const data_t &data)
324 {
325 fDimRain.setData(data);
326 fDimRain.Update(tm);
327 }
328
329public:
330 ConnectionDimRain(ba::io_service& ioservice, MessageImp &imp) :
331 ConnectionRain(ioservice, imp),
332 fDimRain("RAIN_SENSOR/DATA", "F:1;X:1",
333 "|rain[float]:Rain"
334 "|count:Number of sensor requests")
335 {
336 }
337};
338
339// ------------------------------------------------------------------------
340
341template <class T, class S>
342class StateMachineRain : public StateMachineAsio<T>
343{
344private:
345 S fRain;
346
347 bool CheckEventSize(size_t has, const char *name, size_t size)
348 {
349 if (has==size)
350 return true;
351
352 ostringstream msg;
353 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
354 T::Fatal(msg);
355 return false;
356 }
357
358 int SetVerbosity(const EventImp &evt)
359 {
360 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
361 return T::kSM_FatalError;
362
363 fRain.SetVerbose(evt.GetBool());
364
365 return T::GetCurrentState();
366 }
367
368 int SetDebugRx(const EventImp &evt)
369 {
370 if (!CheckEventSize(evt.GetSize(), "SetDebugRx", 1))
371 return T::kSM_FatalError;
372
373 fRain.SetDebugRx(evt.GetBool());
374
375 return T::GetCurrentState();
376 }
377/*
378 int Disconnect()
379 {
380 // Close all connections
381 fRain.PostClose(false);
382
383 return T::GetCurrentState();
384 }
385
386 int Reconnect(const EventImp &evt)
387 {
388 // Close all connections to supress the warning in SetEndpoint
389 fRain.PostClose(false);
390
391 // Now wait until all connection have been closed and
392 // all pending handlers have been processed
393 ba::io_service::poll();
394
395 if (evt.GetBool())
396 fRain.SetEndpoint(evt.GetString());
397
398 // Now we can reopen the connection
399 fRain.PostClose(true);
400
401 return T::GetCurrentState();
402 }
403*/
404 int Execute()
405 {
406 return fRain.GetState();
407 }
408
409public:
410 StateMachineRain(ostream &out=cout) :
411 StateMachineAsio<T>(out, "RAIN_SENSOR"), fRain(*this, *this)
412 {
413 // State names
414 T::AddStateName(RainSensor::State::kDisconnected, "NoConnection",
415 "No connection to web-server could be established recently");
416
417 T::AddStateName(RainSensor::State::kConnected, "Connected",
418 "Connection established, but no valid data received");
419
420 T::AddStateName(RainSensor::State::kValid, "Valid",
421 "Connection established, received data valid");
422
423 // Verbosity commands
424 T::AddEvent("SET_VERBOSE", "B")
425 (bind(&StateMachineRain::SetVerbosity, this, placeholders::_1))
426 ("set verbosity state"
427 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
428
429 T::AddEvent("SET_DEBUG_RX", "B")
430 (bind(&StateMachineRain::SetDebugRx, this, placeholders::_1))
431 ("enable debugging for received data"
432 "|debug-rx[bool]:disable or enable verbosity for received data (yes/no)");
433/*
434 // Conenction commands
435 AddEvent("DISCONNECT")
436 (bind(&StateMachineRain::Disconnect, this))
437 ("disconnect from ethernet");
438
439 AddEvent("RECONNECT", "O")
440 (bind(&StateMachineRain::Reconnect, this, placeholders::_1))
441 ("(Re)connect ethernet connection to FTM, a new address can be given"
442 "|[host][string]:new ethernet address in the form <host:port>");
443*/
444 }
445
446 int EvalOptions(Configuration &conf)
447 {
448 const string user = conf.Has("user") ? conf.Get<string>("user") : "";
449 const string pass = conf.Has("password") ? conf.Get<string>("password") : "";
450
451 fRain.SetInterval(conf.Get<uint16_t>("interval"));
452 fRain.SetDebugTx(conf.Get<bool>("debug-tx"));
453 fRain.SetDebugRx(conf.Get<bool>("debug-rx"));
454 fRain.SetSite(conf.Get<string>("url"));
455 fRain.SetEndpoint(conf.Get<string>("addr"));
456 fRain.SetAuthentication(user, pass);
457 fRain.StartConnect();
458
459 return -1;
460 }
461};
462
463
464
465// ------------------------------------------------------------------------
466
467#include "Main.h"
468
469
470template<class T, class S, class R>
471int RunShell(Configuration &conf)
472{
473 return Main::execute<T, StateMachineRain<S, R>>(conf);
474}
475
476void SetupConfiguration(Configuration &conf)
477{
478 po::options_description control("MAGIC rain sensor connection");
479 control.add_options()
480 ("no-dim,d", po_switch(), "Disable dim services")
481 ("addr,a", var<string>("www.magic.iac.es:80"), "Network address of Cosy")
482 ("url,u", var<string>("/site/weather/rain_current.json"), "File name and path to load")
483 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
484 ("interval,i", var<uint16_t>(60), "Interval between two updates on the server in seconds")
485 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
486 ("debug-rx", po_bool(), "Enable debugging of ethernet receptions.")
487 ("user", var<string>(), "User name for authentication.")
488 ("password", var<string>(), "Password for authentication.")
489 ;
490
491 conf.AddOptions(control);
492}
493
494/*
495 Extract usage clause(s) [if any] for SYNOPSIS.
496 Translators: "Usage" and "or" here are patterns (regular expressions) which
497 are used to match the usage synopsis in program output. An example from cp
498 (GNU coreutils) which contains both strings:
499 Usage: cp [OPTION]... [-T] SOURCE DEST
500 or: cp [OPTION]... SOURCE... DIRECTORY
501 or: cp [OPTION]... -t DIRECTORY SOURCE...
502 */
503void PrintUsage()
504{
505 cout <<
506 "The rainsensor is an interface to the MAGIC rainsensor data.\n"
507 "\n"
508 "The default is that the program is started without user intercation. "
509 "All actions are supposed to arrive as DimCommands. Using the -c "
510 "option, a local shell can be initialized. With h or help a short "
511 "help message about the usuage can be brought to the screen.\n"
512 "\n"
513 "Usage: rainsensor [-c type] [OPTIONS]\n"
514 " or: rainsensor [OPTIONS]\n";
515 cout << endl;
516}
517
518void PrintHelp()
519{
520// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
521
522 /* Additional help text which is printed after the configuration
523 options goes here */
524
525 /*
526 cout << "bla bla bla" << endl << endl;
527 cout << endl;
528 cout << "Environment:" << endl;
529 cout << "environment" << endl;
530 cout << endl;
531 cout << "Examples:" << endl;
532 cout << "test exam" << endl;
533 cout << endl;
534 cout << "Files:" << endl;
535 cout << "files" << endl;
536 cout << endl;
537 */
538}
539
540int main(int argc, const char* argv[])
541{
542 Configuration conf(argv[0]);
543 conf.SetPrintUsage(PrintUsage);
544 Main::SetupConfiguration(conf);
545 SetupConfiguration(conf);
546
547 if (!conf.DoParse(argc, argv, PrintHelp))
548 return 127;
549
550 //try
551 {
552 // No console access at all
553 if (!conf.Has("console"))
554 {
555 if (conf.Get<bool>("no-dim"))
556 return RunShell<LocalStream, StateMachine, ConnectionRain>(conf);
557 else
558 return RunShell<LocalStream, StateMachineDim, ConnectionDimRain>(conf);
559 }
560 // Cosole access w/ and w/o Dim
561 if (conf.Get<bool>("no-dim"))
562 {
563 if (conf.Get<int>("console")==0)
564 return RunShell<LocalShell, StateMachine, ConnectionRain>(conf);
565 else
566 return RunShell<LocalConsole, StateMachine, ConnectionRain>(conf);
567 }
568 else
569 {
570 if (conf.Get<int>("console")==0)
571 return RunShell<LocalShell, StateMachineDim, ConnectionDimRain>(conf);
572 else
573 return RunShell<LocalConsole, StateMachineDim, ConnectionDimRain>(conf);
574 }
575 }
576 /*catch (std::exception& e)
577 {
578 cerr << "Exception: " << e.what() << endl;
579 return -1;
580 }*/
581
582 return 0;
583}
Note: See TracBrowser for help on using the repository browser.