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

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