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

Last change on this file since 13918 was 13867, checked in by tbretz, 12 years ago
Updated some strange namings
File size: 17.2 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
231 PostMessage(cmd);
232 }
233
234 void Request()
235 {
236 PostRequest();
237
238 fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval/2));
239 fKeepAlive.async_wait(boost::bind(&ConnectionWeather::HandleRequest,
240 this, dummy::error));
241 }
242
243 void HandleRequest(const bs::error_code &error)
244 {
245 // 125: Operation canceled (bs::error_code(125, bs::system_category))
246 if (error && error!=ba::error::basic_errors::operation_aborted)
247 {
248 ostringstream str;
249 str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl;
250 Error(str);
251
252 PostClose(false);
253 return;
254 }
255
256 if (!is_open())
257 {
258 // For example: Here we could schedule a new accept if we
259 // would not want to allow two connections at the same time.
260 PostClose(true);
261 return;
262 }
263
264 // Check whether the deadline has passed. We compare the deadline
265 // against the current time since a new asynchronous operation
266 // may have moved the deadline before this actor had a chance
267 // to run.
268 if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now())
269 return;
270
271 Request();
272 }
273
274
275private:
276 // This is called when a connection was established
277 void ConnectionEstablished()
278 {
279 Request();
280 StartReadReport();
281 }
282
283public:
284
285 static const uint16_t kMaxAddr;
286
287public:
288 ConnectionWeather(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
289 fIsVerbose(true), fLastReport(Time::none), fLastReception(Time::none), fKeepAlive(ioservice)
290 {
291 SetLogStream(&imp);
292 }
293
294 void SetVerbose(bool b)
295 {
296 fIsVerbose = b;
297 Connection::SetVerbose(b);
298 }
299
300 void SetInterval(uint16_t i)
301 {
302 fInterval = i;
303 }
304
305 void SetSite(const string &site)
306 {
307 fSite = site;
308 }
309
310 int GetState() const
311 {
312 if (fLastReport.IsValid() && fLastReport+boost::posix_time::seconds(fInterval*2)>Time())
313 return 3;
314
315 if (fLastReception.IsValid() && fLastReception+boost::posix_time::seconds(fInterval*2)>Time())
316 return 2;
317
318 return 1;
319
320 }
321};
322
323const uint16_t ConnectionWeather::kMaxAddr = 0xfff;
324
325// ------------------------------------------------------------------------
326
327#include "DimDescriptionService.h"
328
329class ConnectionDimWeather : public ConnectionWeather
330{
331private:
332
333 DimDescribedService fDimWeather;
334
335 virtual void UpdateWeather(const Time &t, const DimWeather &data)
336 {
337 fDimWeather.setData(&data, sizeof(DimWeather));
338 fDimWeather.Update(t);
339 }
340
341public:
342 ConnectionDimWeather(ba::io_service& ioservice, MessageImp &imp) :
343 ConnectionWeather(ioservice, imp),
344 fDimWeather("MAGIC_WEATHER/DATA", "S:1;F:1;F:1;F:1;F:1;F:1;F:1;F:1",
345 "|stat:Status"
346 "|T[deg C]:Temperature"
347 "|T_dew[deg C]:Dew point"
348 "|H[%]:Humidity"
349 "|P[hPa]:Air pressure"
350 "|v[km/h]:Wind speed"
351 "|v_max[km/h]:Wind gusts"
352 "|d[deg]:Wind direction (N-E)")
353 {
354 }
355};
356
357// ------------------------------------------------------------------------
358
359template <class T, class S>
360class StateMachineWeather : public T, public ba::io_service, public ba::io_service::work
361{
362private:
363 S fWeather;
364
365 enum states_t
366 {
367 kStateDisconnected = 1,
368 kStateConnected,
369 kStateReceiving,
370 };
371
372 bool CheckEventSize(size_t has, const char *name, size_t size)
373 {
374 if (has==size)
375 return true;
376
377 ostringstream msg;
378 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
379 T::Fatal(msg);
380 return false;
381 }
382
383 int SetVerbosity(const EventImp &evt)
384 {
385 if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
386 return T::kSM_FatalError;
387
388 fWeather.SetVerbose(evt.GetBool());
389
390 return T::GetCurrentState();
391 }
392/*
393 int Disconnect()
394 {
395 // Close all connections
396 fWeather.PostClose(false);
397
398 return T::GetCurrentState();
399 }
400
401 int Reconnect(const EventImp &evt)
402 {
403 // Close all connections to supress the warning in SetEndpoint
404 fWeather.PostClose(false);
405
406 // Now wait until all connection have been closed and
407 // all pending handlers have been processed
408 poll();
409
410 if (evt.GetBool())
411 fWeather.SetEndpoint(evt.GetString());
412
413 // Now we can reopen the connection
414 fWeather.PostClose(true);
415
416 return T::GetCurrentState();
417 }
418*/
419 int Execute()
420 {
421 // Dispatch (execute) at most one handler from the queue. In contrary
422 // to run_one(), it doesn't wait until a handler is available
423 // which can be dispatched, so poll_one() might return with 0
424 // handlers dispatched. The handlers are always dispatched/executed
425 // synchronously, i.e. within the call to poll_one()
426 poll_one();
427
428 return fWeather.GetState();
429 }
430
431
432public:
433 StateMachineWeather(ostream &out=cout) :
434 T(out, "MAGIC_WEATHER"), ba::io_service::work(static_cast<ba::io_service&>(*this)),
435 fWeather(*this, *this)
436 {
437 // ba::io_service::work is a kind of keep_alive for the loop.
438 // It prevents the io_service to go to stopped state, which
439 // would prevent any consecutive calls to run()
440 // or poll() to do nothing. reset() could also revoke to the
441 // previous state but this might introduce some overhead of
442 // deletion and creation of threads and more.
443
444 // State names
445 AddStateName(kStateDisconnected, "NoConnection",
446 "No connection to web-server could be established recently");
447
448 AddStateName(kStateConnected, "Invalid",
449 "Connection to webserver can be established, but received data is not recent or invalid");
450
451 AddStateName(kStateReceiving, "Valid",
452 "Connection to webserver can be established, receint data received");
453
454 // Verbosity commands
455 T::AddEvent("SET_VERBOSE", "B")
456 (bind(&StateMachineWeather::SetVerbosity, this, placeholders::_1))
457 ("set verbosity state"
458 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
459/*
460 // Conenction commands
461 AddEvent("DISCONNECT")
462 (bind(&StateMachineWeather::Disconnect, this))
463 ("disconnect from ethernet");
464
465 AddEvent("RECONNECT", "O")
466 (bind(&StateMachineWeather::Reconnect, this, placeholders::_1))
467 ("(Re)connect ethernet connection to FTM, a new address can be given"
468 "|[host][string]:new ethernet address in the form <host:port>");
469*/
470 }
471
472 int EvalOptions(Configuration &conf)
473 {
474 fWeather.SetVerbose(!conf.Get<bool>("quiet"));
475 fWeather.SetInterval(conf.Get<uint16_t>("interval"));
476 fWeather.SetDebugTx(conf.Get<bool>("debug-tx"));
477 fWeather.SetSite(conf.Get<string>("url"));
478 fWeather.SetEndpoint(conf.Get<string>("addr"));
479 fWeather.StartConnect();
480
481 return -1;
482 }
483};
484
485// ------------------------------------------------------------------------
486
487#include "Main.h"
488
489
490template<class T, class S, class R>
491int RunShell(Configuration &conf)
492{
493 return Main::execute<T, StateMachineWeather<S, R>>(conf);
494}
495
496void SetupConfiguration(Configuration &conf)
497{
498 po::options_description control("MAGIC weather control options");
499 control.add_options()
500 ("no-dim,d", po_switch(), "Disable dim services")
501 ("addr,a", var<string>("www.magic.iac.es:80"), "Network address of Cosy")
502 ("url,u", var<string>("/site/weather/weather_data.txt"), "File name and path to load")
503 ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.")
504 ("interval,i", var<uint16_t>(30), "Interval between two updates on the server in seconds")
505 ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.")
506 ;
507
508 conf.AddOptions(control);
509}
510
511/*
512 Extract usage clause(s) [if any] for SYNOPSIS.
513 Translators: "Usage" and "or" here are patterns (regular expressions) which
514 are used to match the usage synopsis in program output. An example from cp
515 (GNU coreutils) which contains both strings:
516 Usage: cp [OPTION]... [-T] SOURCE DEST
517 or: cp [OPTION]... SOURCE... DIRECTORY
518 or: cp [OPTION]... -t DIRECTORY SOURCE...
519 */
520void PrintUsage()
521{
522 cout <<
523 "The magicweather is an interface to the MAGIC weather data.\n"
524 "\n"
525 "The default is that the program is started without user intercation. "
526 "All actions are supposed to arrive as DimCommands. Using the -c "
527 "option, a local shell can be initialized. With h or help a short "
528 "help message about the usuage can be brought to the screen.\n"
529 "\n"
530 "Usage: magicweather [-c type] [OPTIONS]\n"
531 " or: magicweather [OPTIONS]\n";
532 cout << endl;
533}
534
535void PrintHelp()
536{
537// Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>();
538
539 /* Additional help text which is printed after the configuration
540 options goes here */
541
542 /*
543 cout << "bla bla bla" << endl << endl;
544 cout << endl;
545 cout << "Environment:" << endl;
546 cout << "environment" << endl;
547 cout << endl;
548 cout << "Examples:" << endl;
549 cout << "test exam" << endl;
550 cout << endl;
551 cout << "Files:" << endl;
552 cout << "files" << endl;
553 cout << endl;
554 */
555}
556
557int main(int argc, const char* argv[])
558{
559 Configuration conf(argv[0]);
560 conf.SetPrintUsage(PrintUsage);
561 Main::SetupConfiguration(conf);
562 SetupConfiguration(conf);
563
564 if (!conf.DoParse(argc, argv, PrintHelp))
565 return -1;
566
567 //try
568 {
569 // No console access at all
570 if (!conf.Has("console"))
571 {
572 if (conf.Get<bool>("no-dim"))
573 return RunShell<LocalStream, StateMachine, ConnectionWeather>(conf);
574 else
575 return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf);
576 }
577 // Cosole access w/ and w/o Dim
578 if (conf.Get<bool>("no-dim"))
579 {
580 if (conf.Get<int>("console")==0)
581 return RunShell<LocalShell, StateMachine, ConnectionWeather>(conf);
582 else
583 return RunShell<LocalConsole, StateMachine, ConnectionWeather>(conf);
584 }
585 else
586 {
587 if (conf.Get<int>("console")==0)
588 return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf);
589 else
590 return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf);
591 }
592 }
593 /*catch (std::exception& e)
594 {
595 cerr << "Exception: " << e.what() << endl;
596 return -1;
597 }*/
598
599 return 0;
600}
Note: See TracBrowser for help on using the repository browser.