source: trunk/FACT++/src/magicweather.cc@ 13210

Last change on this file since 13210 was 13210, checked in by tbretz, 14 years ago
Removed all the unnecessary log-messages.
File size: 17.4 KB
Line 
1#include <boost/bind.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 "Connection.h"
13#include "LocalControl.h"
14#include "Configuration.h"
15#include "Timers.h"
16#include "Console.h"
17#include "Converter.h"
18
19#include "tools.h"
20
21
22namespace ba = boost::asio;
23namespace bs = boost::system;
24namespace dummy = ba::placeholders;
25
26using namespace std;
27
28// ------------------------------------------------------------------------
29
30struct DimWeather
31{
32 DimWeather() { memset(this, 0, sizeof(DimWeather)); }
33
34 uint16_t fStatus;
35
36 float fTemp;
37 float fDew;
38 float fHum;
39 float fPress;
40 float fWind;
41 float fGusts;
42 float fDir;
43
44} __attribute__((__packed__));
45
46
47// ------------------------------------------------------------------------
48
49class ConnectionWeather : public Connection
50{
51 uint16_t fInterval;
52
53 bool fIsVerbose;
54
55 string fSite;
56
57 virtual void UpdateWeather(const Time &, const DimWeather &)
58 {
59 }
60
61protected:
62
63 boost::array<char, 4096> fArray;
64
65 Time fLastReport;
66 Time fLastReception;
67
68 void HandleRead(const boost::system::error_code& err, size_t bytes_received)
69 {
70 // Do not schedule a new read if the connection failed.
71 if (bytes_received==0 || err)
72 {
73 if (err==ba::error::eof)
74 Warn("Connection closed by remote host.");
75
76 // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
77 // 125: Operation canceled
78 if (err && err!=ba::error::eof && // Connection closed by remote host
79 err!=ba::error::basic_errors::not_connected && // Connection closed by remote host
80 err!=ba::error::basic_errors::operation_aborted) // Connection closed by us
81 {
82 ostringstream str;
83 str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
84 Error(str);
85 }
86 PostClose(err!=ba::error::basic_errors::operation_aborted);
87 return;
88 }
89
90 fLastReception = Time();
91
92 const string str(fArray.data(), bytes_received);
93 memset(fArray.data(), 0, fArray.size());
94
95 if (fIsVerbose)
96 Out() << str << endl;
97
98 bool isheader = true;
99
100 DimWeather data;
101
102 int hh=0, mm=0, ss=0, y=0, m=0, d=0;
103
104 bool keepalive = false;
105
106 stringstream is(str);
107 string line;
108 while (getline(is, line))
109 {
110 if (line.size()==1 && line[0]==13)
111 {
112 isheader = false;
113 continue;
114 }
115
116 if (isheader)
117 {
118 const size_t p = line.find_first_of(": ");
119 if (p==string::npos)
120 continue;
121
122 std::transform(line.begin(), line.end(), line.begin(), (int(&)(int))std::tolower);
123
124 const string key = line.substr(0, p);
125 const string val = line.substr(p+2);
126
127 if (key=="connection" && val=="keep-alive")
128 keepalive = true;
129 }
130 else
131 {
132 if (line.substr(0, 2)=="ST")
133 data.fStatus = stoi(line.substr(2));
134
135 if (line.substr(0, 2)=="TE")
136 data.fTemp = stof(line.substr(2));
137
138 if (line.substr(0, 2)=="DP")
139 data.fDew = stof(line.substr(2));
140
141 if (line.substr(0, 3)=="HUM")
142 data.fHum = stof(line.substr(3));
143
144 if (line.substr(0, 2)=="WS")
145 data.fWind = stof(line.substr(2));
146
147 if (line.substr(0, 3)=="MWD")
148 data.fDir = stof(line.substr(3));
149
150 if (line.substr(0, 2)=="WP")
151 data.fGusts = stof(line.substr(2));
152
153 if (line.substr(0, 5)=="PRESS")
154 data.fPress = stof(line.substr(5));
155
156 if (line.substr(0, 4)=="HOUR")
157 hh = stoi(line.substr(4));
158
159 if (line.substr(0, 6)=="MINUTS")
160 mm = stoi(line.substr(6));
161
162 if (line.substr(0, 7)=="SECONDS")
163 ss = stoi(line.substr(7));
164
165 if (line.substr(0, 4)=="YEAR")
166 y = stoi(line.substr(4));
167
168 if (line.substr(0, 5)=="MONTH")
169 m = stoi(line.substr(5));
170
171 if (line.substr(0, 3)=="DAY")
172 d = stoi(line.substr(3));
173 }
174 }
175
176 if (!keepalive)
177 PostClose(false);
178
179 try
180 {
181 const Time tm = Time(2000+y, m, d, hh, mm, ss);
182 if (tm==fLastReport)
183 return;
184
185 ostringstream msg;
186 msg << tm.GetAsStr("%H:%M:%S") << "[" << data.fStatus << "]:"
187 << " T=" << data.fTemp << "°C"
188 << " H=" << data.fHum << "%"
189 << " P=" << data.fPress << "hPa"
190 << " Td=" << data.fDew << "°C"
191 << " V=" << data.fWind << "km/h"
192 << " Vmax=" << data.fGusts << "km/h"
193 << " dir=" << data.fDir << "°";
194 Message(msg);
195
196 UpdateWeather(tm, data);
197
198 fLastReport = tm;
199 }
200 catch (const exception &e)
201 {
202 Warn("Corrupted time received.");
203 }
204
205 }
206
207 void StartReadReport()
208 {
209 async_read_some(ba::buffer(fArray),
210 boost::bind(&ConnectionWeather::HandleRead, this,
211 dummy::error, dummy::bytes_transferred));
212 }
213
214 boost::asio::deadline_timer fKeepAlive;
215
216 void PostRequest()
217 {
218 const string cmd =
219 "GET "+fSite+" HTTP/1.1\r\n"
220 "Accept: */*\r\n"
221 "Content-Type: application/octet-stream\r\n"
222 "User-Agent: FACT\r\n"
223 "Host: www.fact-project.org\r\n"
224 "Pragma: no-cache\r\n"
225 "Cache-Control: no-cache\r\n"
226 "Expires: 0\r\n"
227 "Connection: Keep-Alive\r\n"
228 "Cache-Control: max-age=0\r\n"
229 "\r\n";
230 PostMessage(cmd);
231 }
232
233 void Request()
234 {
235 PostRequest();
236
237 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval/2));
238 fKeepAlive.async_wait(boost::bind(&ConnectionWeather::HandleRequest,
239 this, dummy::error));
240 }
241
242 void HandleRequest(const bs::error_code &error)
243 {
244 // 125: Operation canceled (bs::error_code(125, bs::system_category))
245 if (error && error!=ba::error::basic_errors::operation_aborted)
246 {
247 ostringstream str;
248 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
249 Error(str);
250
251 PostClose(false);
252 return;
253 }
254
255 if (!is_open())
256 {
257 // For example: Here we could schedule a new accept if we
258 // would not want to allow two connections at the same time.
259 PostClose(true);
260 return;
261 }
262
263 // Check whether the deadline has passed. We compare the deadline
264 // against the current time since a new asynchronous operation
265 // may have moved the deadline before this actor had a chance
266 // to run.
267 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
268 return;
269
270 Request();
271 }
272
273
274private:
275 // This is called when a connection was established
276 void ConnectionEstablished()
277 {
278 Request();
279 StartReadReport();
280 }
281
282public:
283
284 static const uint16_t kMaxAddr;
285
286public:
287 ConnectionWeather(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
288 fIsVerbose(true), fKeepAlive(ioservice)
289 {
290 SetLogStream(&imp);
291 }
292
293 void SetVerbose(bool b)
294 {
295 fIsVerbose = b;
296 Connection::SetVerbose(b);
297 }
298
299 void SetInterval(uint16_t i)
300 {
301 fInterval = i;
302 }
303
304 void SetSite(const string &site)
305 {
306 fSite = site;
307 }
308
309 int GetState() const
310 {
311 if (fLastReport+boost::posix_time::seconds(fInterval*2)>Time())
312 return 3;
313 if (fLastReception+boost::posix_time::seconds(fInterval*2)>Time())
314 return 2;
315 return 1;
316
317 }
318};
319
320const uint16_t ConnectionWeather::kMaxAddr = 0xfff;
321
322// ------------------------------------------------------------------------
323
324#include "DimDescriptionService.h"
325
326class ConnectionDimDrive : public ConnectionWeather
327{
328private:
329
330 DimDescribedService fDimWeather;
331
332 virtual void UpdateWeather(const Time &t, const DimWeather &data)
333 {
334 fDimWeather.setData(&data, sizeof(DimWeather));
335 fDimWeather.Update(t);
336 }
337
338public:
339 ConnectionDimDrive(ba::io_service& ioservice, MessageImp &imp) :
340 ConnectionWeather(ioservice, imp),
341 fDimWeather("MAGIC_WEATHER/DATA", "S:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1",
342 "|stat:Status"
343 "|T[deg C]:Temperature"
344 "|T_dew[deg C]:Dew point"
345 "|H[%]:Humidity"
346 "|P[hPa]:Air pressure"
347 "|v[km/h]:Wind speed"
348 "|v_max[km/h]:Wind gusts"
349 "|d[deg]:Wind direction (N-E)")
350 {
351 }
352};
353
354// ------------------------------------------------------------------------
355
356template <class T, class S>
357class StateMachineDrive : public T, public ba::io_service, public ba::io_service::work
358{
359 int Wrap(boost::function<void()> f)
360 {
361 f();
362 return T::GetCurrentState();
363 }
364
365 boost::function<int(const EventImp &)> Wrapper(boost::function<void()> func)
366 {
367 return bind(&StateMachineDrive::Wrap, this, func);
368 }
369
370private:
371 S fWeather;
372
373 enum states_t
374 {
375 kStateDisconnected = 1,
376 kStateConnected,
377 kStateReceiving,
378 };
379
380 bool CheckEventSize(size_t has, const char *name, size_t size)
381 {
382 if (has==size)
383 return true;
384
385 ostringstream msg;
386 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
387 T::Fatal(msg);
388 return false;
389 }
390
391 int SetVerbosity(const EventImp &evt)
392 {
393 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
394 return T::kSM_FatalError;
395
396 fWeather.SetVerbose(evt.GetBool());
397
398 return T::GetCurrentState();
399 }
400/*
401 int Disconnect()
402 {
403 // Close all connections
404 fWeather.PostClose(false);
405
406 return T::GetCurrentState();
407 }
408
409 int Reconnect(const EventImp &evt)
410 {
411 // Close all connections to supress the warning in SetEndpoint
412 fWeather.PostClose(false);
413
414 // Now wait until all connection have been closed and
415 // all pending handlers have been processed
416 poll();
417
418 if (evt.GetBool())
419 fWeather.SetEndpoint(evt.GetString());
420
421 // Now we can reopen the connection
422 fWeather.PostClose(true);
423
424 return T::GetCurrentState();
425 }
426*/
427 int Execute()
428 {
429 // Dispatch (execute) at most one handler from the queue. In contrary
430 // to run_one(), it doesn't wait until a handler is available
431 // which can be dispatched, so poll_one() might return with 0
432 // handlers dispatched. The handlers are always dispatched/executed
433 // synchronously, i.e. within the call to poll_one()
434 poll_one();
435
436 return fWeather.GetState();
437 }
438
439
440public:
441 StateMachineDrive(ostream &out=cout) :
442 T(out, "MAGIC_WEATHER"), ba::io_service::work(static_cast<ba::io_service&>(*this)),
443 fWeather(*this, *this)
444 {
445 // ba::io_service::work is a kind of keep_alive for the loop.
446 // It prevents the io_service to go to stopped state, which
447 // would prevent any consecutive calls to run()
448 // or poll() to do nothing. reset() could also revoke to the
449 // previous state but this might introduce some overhead of
450 // deletion and creation of threads and more.
451
452 // State names
453 AddStateName(kStateDisconnected, "Disconnected",
454 "No connection with web-server recently");
455
456 AddStateName(kStateConnected, "Connected",
457 "Connection to webserver can be established, but received data is not recent");
458
459 AddStateName(kStateReceiving, "Valid",
460 "Connection to webserver can be established, receint data received");
461
462 // Verbosity commands
463 T::AddEvent("SET_VERBOSE", "B")
464 (bind(&StateMachineDrive::SetVerbosity, this, placeholders::_1))
465 ("set verbosity state"
466 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
467/*
468 // Conenction commands
469 AddEvent("DISCONNECT")
470 (bind(&StateMachineDrive::Disconnect, this))
471 ("disconnect from ethernet");
472
473 AddEvent("RECONNECT", "O")
474 (bind(&StateMachineDrive::Reconnect, this, placeholders::_1))
475 ("(Re)connect ethernet connection to FTM, a new address can be given"
476 "|[host][string]:new ethernet address in the form <host:port>");
477*/
478 fWeather.StartConnect();
479 }
480
481 void SetEndpoint(const string &url)
482 {
483 fWeather.SetEndpoint(url);
484 }
485
486 int EvalOptions(Configuration &conf)
487 {
488 fWeather.SetVerbose(!conf.Get<bool>("quiet"));
489 fWeather.SetInterval(conf.Get<uint16_t>("interval"));
490 fWeather.SetDebugTx(conf.Get<bool>("debug-tx"));
491 fWeather.SetSite(conf.Get<string>("url"));
492
493 SetEndpoint(conf.Get<string>("addr"));
494
495
496 return -1;
497 }
498};
499
500// ------------------------------------------------------------------------
501
502#include "Main.h"
503
504
505template<class T, class S, class R>
506int RunShell(Configuration &conf)
507{
508 return Main::execute<T, StateMachineDrive<S, R>>(conf);
509}
510
511void SetupConfiguration(Configuration &conf)
512{
513 po::options_description control("Drive control options");
514 control.add_options()
515 ("no-dim,d", po_switch(), "Disable dim services")
516 ("addr,a", var<string>("www.magic.iac.es:80"), "Network address of Cosy")
517 ("url,u", var<string>("/site/weather/weather_data.txt"), "File name and path to load")
518 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
519 ("interval,i", var<uint16_t>(30), "Interval between two updates on the server in seconds")
520 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
521 ;
522
523 conf.AddOptions(control);
524}
525
526/*
527 Extract usage clause(s) [if any] for SYNOPSIS.
528 Translators: "Usage" and "or" here are patterns (regular expressions) which
529 are used to match the usage synopsis in program output. An example from cp
530 (GNU coreutils) which contains both strings:
531 Usage: cp [OPTION]... [-T] SOURCE DEST
532 or: cp [OPTION]... SOURCE... DIRECTORY
533 or: cp [OPTION]... -t DIRECTORY SOURCE...
534 */
535void PrintUsage()
536{
537 cout <<
538 "The drivectrl is an interface to cosy.\n"
539 "\n"
540 "The default is that the program is started without user intercation. "
541 "All actions are supposed to arrive as DimCommands. Using the -c "
542 "option, a local shell can be initialized. With h or help a short "
543 "help message about the usuage can be brought to the screen.\n"
544 "\n"
545 "Usage: drivectrl [-c type] [OPTIONS]\n"
546 " or: drivectrl [OPTIONS]\n";
547 cout << endl;
548}
549
550void PrintHelp()
551{
552// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
553
554 /* Additional help text which is printed after the configuration
555 options goes here */
556
557 /*
558 cout << "bla bla bla" << endl << endl;
559 cout << endl;
560 cout << "Environment:" << endl;
561 cout << "environment" << endl;
562 cout << endl;
563 cout << "Examples:" << endl;
564 cout << "test exam" << endl;
565 cout << endl;
566 cout << "Files:" << endl;
567 cout << "files" << endl;
568 cout << endl;
569 */
570}
571
572int main(int argc, const char* argv[])
573{
574 Configuration conf(argv[0]);
575 conf.SetPrintUsage(PrintUsage);
576 Main::SetupConfiguration(conf);
577 SetupConfiguration(conf);
578
579 if (!conf.DoParse(argc, argv, PrintHelp))
580 return -1;
581
582 //try
583 {
584 // No console access at all
585 if (!conf.Has("console"))
586 {
587 if (conf.Get<bool>("no-dim"))
588 return RunShell<LocalStream, StateMachine, ConnectionWeather>(conf);
589 else
590 return RunShell<LocalStream, StateMachineDim, ConnectionDimDrive>(conf);
591 }
592 // Cosole access w/ and w/o Dim
593 if (conf.Get<bool>("no-dim"))
594 {
595 if (conf.Get<int>("console")==0)
596 return RunShell<LocalShell, StateMachine, ConnectionWeather>(conf);
597 else
598 return RunShell<LocalConsole, StateMachine, ConnectionWeather>(conf);
599 }
600 else
601 {
602 if (conf.Get<int>("console")==0)
603 return RunShell<LocalShell, StateMachineDim, ConnectionDimDrive>(conf);
604 else
605 return RunShell<LocalConsole, StateMachineDim, ConnectionDimDrive>(conf);
606 }
607 }
608 /*catch (std::exception& e)
609 {
610 cerr << "Exception: " << e.what() << endl;
611 return -1;
612 }*/
613
614 return 0;
615}
Note: See TracBrowser for help on using the repository browser.