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

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