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

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