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

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