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

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