1 | #include <boost/array.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 "StateMachineAsio.h" |
---|
13 | #include "Connection.h" |
---|
14 | #include "LocalControl.h" |
---|
15 | #include "Configuration.h" |
---|
16 | #include "Timers.h" |
---|
17 | #include "Console.h" |
---|
18 | |
---|
19 | #include "tools.h" |
---|
20 | |
---|
21 | #include "HeadersMagicWeather.h" |
---|
22 | |
---|
23 | namespace ba = boost::asio; |
---|
24 | namespace bs = boost::system; |
---|
25 | namespace dummy = ba::placeholders; |
---|
26 | |
---|
27 | using namespace std; |
---|
28 | using namespace MagicWeather; |
---|
29 | |
---|
30 | // ------------------------------------------------------------------------ |
---|
31 | |
---|
32 | class ConnectionWeather : public Connection |
---|
33 | { |
---|
34 | uint16_t fInterval; |
---|
35 | |
---|
36 | bool fIsVerbose; |
---|
37 | |
---|
38 | string fSite; |
---|
39 | |
---|
40 | virtual void UpdateWeather(const Time &, const DimWeather &) |
---|
41 | { |
---|
42 | } |
---|
43 | |
---|
44 | protected: |
---|
45 | |
---|
46 | boost::array<char, 4096> fArray; |
---|
47 | |
---|
48 | Time fLastReport; |
---|
49 | Time fLastReception; |
---|
50 | |
---|
51 | void HandleRead(const boost::system::error_code& err, size_t bytes_received) |
---|
52 | { |
---|
53 | // Do not schedule a new read if the connection failed. |
---|
54 | if (bytes_received==0 || err) |
---|
55 | { |
---|
56 | if (err==ba::error::eof) |
---|
57 | Warn("Connection closed by remote host."); |
---|
58 | |
---|
59 | // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category)) |
---|
60 | // 125: Operation canceled |
---|
61 | if (err && err!=ba::error::eof && // Connection closed by remote host |
---|
62 | err!=ba::error::basic_errors::not_connected && // Connection closed by remote host |
---|
63 | err!=ba::error::basic_errors::operation_aborted) // Connection closed by us |
---|
64 | { |
---|
65 | ostringstream str; |
---|
66 | str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl; |
---|
67 | Error(str); |
---|
68 | } |
---|
69 | PostClose(err!=ba::error::basic_errors::operation_aborted); |
---|
70 | return; |
---|
71 | } |
---|
72 | |
---|
73 | fLastReception = Time(); |
---|
74 | |
---|
75 | const string str(fArray.data(), bytes_received); |
---|
76 | memset(fArray.data(), 0, fArray.size()); |
---|
77 | |
---|
78 | if (fIsVerbose) |
---|
79 | Out() << str << endl; |
---|
80 | |
---|
81 | bool isheader = true; |
---|
82 | |
---|
83 | DimWeather data; |
---|
84 | |
---|
85 | int hh=0, mm=0, ss=0, y=0, m=0, d=0; |
---|
86 | |
---|
87 | bool keepalive = false; |
---|
88 | |
---|
89 | stringstream is(str); |
---|
90 | string line; |
---|
91 | while (getline(is, line)) |
---|
92 | { |
---|
93 | if (line.size()==1 && line[0]==13) |
---|
94 | { |
---|
95 | isheader = false; |
---|
96 | continue; |
---|
97 | } |
---|
98 | |
---|
99 | if (isheader) |
---|
100 | { |
---|
101 | const size_t p = line.find_first_of(": "); |
---|
102 | if (p==string::npos) |
---|
103 | continue; |
---|
104 | |
---|
105 | std::transform(line.begin(), line.end(), line.begin(), (int(&)(int))std::tolower); |
---|
106 | |
---|
107 | const string key = line.substr(0, p); |
---|
108 | const string val = line.substr(p+2); |
---|
109 | |
---|
110 | if (key=="connection" && val=="keep-alive") |
---|
111 | keepalive = true; |
---|
112 | } |
---|
113 | else |
---|
114 | { |
---|
115 | if (line.substr(0, 2)=="ST") |
---|
116 | data.fStatus = stoi(line.substr(2)); |
---|
117 | |
---|
118 | if (line.substr(0, 2)=="TE") |
---|
119 | data.fTemp = stof(line.substr(2)); |
---|
120 | |
---|
121 | if (line.substr(0, 2)=="DP") |
---|
122 | data.fDew = stof(line.substr(2)); |
---|
123 | |
---|
124 | if (line.substr(0, 3)=="HUM") |
---|
125 | data.fHum = stof(line.substr(3)); |
---|
126 | |
---|
127 | if (line.substr(0, 2)=="WS") |
---|
128 | data.fWind = stof(line.substr(2)); |
---|
129 | |
---|
130 | if (line.substr(0, 3)=="MWD") |
---|
131 | data.fDir = stof(line.substr(3)); |
---|
132 | |
---|
133 | if (line.substr(0, 2)=="WP") |
---|
134 | data.fGusts = stof(line.substr(2)); |
---|
135 | |
---|
136 | if (line.substr(0, 5)=="PRESS") |
---|
137 | data.fPress = stof(line.substr(5)); |
---|
138 | |
---|
139 | if (line.substr(0, 4)=="HOUR") |
---|
140 | hh = stoi(line.substr(4)); |
---|
141 | |
---|
142 | if (line.substr(0, 6)=="MINUTS") |
---|
143 | mm = stoi(line.substr(6)); |
---|
144 | |
---|
145 | if (line.substr(0, 7)=="SECONDS") |
---|
146 | ss = stoi(line.substr(7)); |
---|
147 | |
---|
148 | if (line.substr(0, 4)=="YEAR") |
---|
149 | y = stoi(line.substr(4)); |
---|
150 | |
---|
151 | if (line.substr(0, 5)=="MONTH") |
---|
152 | m = stoi(line.substr(5)); |
---|
153 | |
---|
154 | if (line.substr(0, 3)=="DAY") |
---|
155 | d = stoi(line.substr(3)); |
---|
156 | } |
---|
157 | } |
---|
158 | |
---|
159 | if (!keepalive) |
---|
160 | PostClose(false); |
---|
161 | |
---|
162 | try |
---|
163 | { |
---|
164 | const Time tm = Time(2000+y, m, d, hh, mm, ss); |
---|
165 | if (tm==fLastReport) |
---|
166 | return; |
---|
167 | |
---|
168 | ostringstream msg; |
---|
169 | msg << tm.GetAsStr("%H:%M:%S") << "[" << data.fStatus << "]:" |
---|
170 | << " T=" << data.fTemp << "\u00b0C" |
---|
171 | << " H=" << data.fHum << "%" |
---|
172 | << " P=" << data.fPress << "hPa" |
---|
173 | << " Td=" << data.fDew << "\u00b0C" |
---|
174 | << " V=" << data.fWind << "km/h" |
---|
175 | << " Vmax=" << data.fGusts << "km/h" |
---|
176 | << " dir=" << data.fDir << "\u00b0"; |
---|
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 | ba::deadline_timer fKeepAlive; |
---|
198 | |
---|
199 | void PostRequest() |
---|
200 | { |
---|
201 | const string cmd = |
---|
202 | "GET "+fSite+" HTTP/1.1\r\n" |
---|
203 | "Accept: */*\r\n" |
---|
204 | "Content-Type: application/octet-stream\r\n" |
---|
205 | "User-Agent: FACT\r\n" |
---|
206 | "Host: www.fact-project.org\r\n" |
---|
207 | "Pragma: no-cache\r\n" |
---|
208 | "Cache-Control: no-cache\r\n" |
---|
209 | "Expires: 0\r\n" |
---|
210 | "Connection: Keep-Alive\r\n" |
---|
211 | "Cache-Control: max-age=0\r\n" |
---|
212 | "\r\n"; |
---|
213 | |
---|
214 | PostMessage(cmd); |
---|
215 | } |
---|
216 | |
---|
217 | void Request() |
---|
218 | { |
---|
219 | PostRequest(); |
---|
220 | |
---|
221 | fKeepAlive.expires_from_now(boost::posix_time::seconds(fInterval/2)); |
---|
222 | fKeepAlive.async_wait(boost::bind(&ConnectionWeather::HandleRequest, |
---|
223 | this, dummy::error)); |
---|
224 | } |
---|
225 | |
---|
226 | void HandleRequest(const bs::error_code &error) |
---|
227 | { |
---|
228 | // 125: Operation canceled (bs::error_code(125, bs::system_category)) |
---|
229 | if (error && error!=ba::error::basic_errors::operation_aborted) |
---|
230 | { |
---|
231 | ostringstream str; |
---|
232 | str << "Write timeout of " << URL() << ": " << error.message() << " (" << error << ")";// << endl; |
---|
233 | Error(str); |
---|
234 | |
---|
235 | PostClose(false); |
---|
236 | return; |
---|
237 | } |
---|
238 | |
---|
239 | if (!is_open()) |
---|
240 | { |
---|
241 | // For example: Here we could schedule a new accept if we |
---|
242 | // would not want to allow two connections at the same time. |
---|
243 | PostClose(true); |
---|
244 | return; |
---|
245 | } |
---|
246 | |
---|
247 | // Check whether the deadline has passed. We compare the deadline |
---|
248 | // against the current time since a new asynchronous operation |
---|
249 | // may have moved the deadline before this actor had a chance |
---|
250 | // to run. |
---|
251 | if (fKeepAlive.expires_at() > ba::deadline_timer::traits_type::now()) |
---|
252 | return; |
---|
253 | |
---|
254 | Request(); |
---|
255 | } |
---|
256 | |
---|
257 | |
---|
258 | private: |
---|
259 | // This is called when a connection was established |
---|
260 | void ConnectionEstablished() |
---|
261 | { |
---|
262 | Request(); |
---|
263 | StartReadReport(); |
---|
264 | } |
---|
265 | |
---|
266 | public: |
---|
267 | |
---|
268 | static const uint16_t kMaxAddr; |
---|
269 | |
---|
270 | public: |
---|
271 | ConnectionWeather(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()), |
---|
272 | fIsVerbose(true), fLastReport(Time::none), fLastReception(Time::none), fKeepAlive(ioservice) |
---|
273 | { |
---|
274 | SetLogStream(&imp); |
---|
275 | } |
---|
276 | |
---|
277 | void SetVerbose(bool b) |
---|
278 | { |
---|
279 | fIsVerbose = b; |
---|
280 | Connection::SetVerbose(b); |
---|
281 | } |
---|
282 | |
---|
283 | void SetInterval(uint16_t i) |
---|
284 | { |
---|
285 | fInterval = i; |
---|
286 | } |
---|
287 | |
---|
288 | void SetSite(const string &site) |
---|
289 | { |
---|
290 | fSite = site; |
---|
291 | } |
---|
292 | |
---|
293 | int GetState() const |
---|
294 | { |
---|
295 | if (fLastReport.IsValid() && fLastReport+boost::posix_time::seconds(fInterval*2)>Time()) |
---|
296 | return 3; |
---|
297 | |
---|
298 | if (fLastReception.IsValid() && fLastReception+boost::posix_time::seconds(fInterval*2)>Time()) |
---|
299 | return 2; |
---|
300 | |
---|
301 | return 1; |
---|
302 | |
---|
303 | } |
---|
304 | }; |
---|
305 | |
---|
306 | const uint16_t ConnectionWeather::kMaxAddr = 0xfff; |
---|
307 | |
---|
308 | // ------------------------------------------------------------------------ |
---|
309 | |
---|
310 | #include "DimDescriptionService.h" |
---|
311 | |
---|
312 | class ConnectionDimWeather : public ConnectionWeather |
---|
313 | { |
---|
314 | private: |
---|
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 | |
---|
324 | public: |
---|
325 | ConnectionDimWeather(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 | |
---|
342 | template <class T, class S> |
---|
343 | class StateMachineWeather : public StateMachineAsio<T> |
---|
344 | { |
---|
345 | private: |
---|
346 | S fWeather; |
---|
347 | |
---|
348 | bool CheckEventSize(size_t has, const char *name, size_t size) |
---|
349 | { |
---|
350 | if (has==size) |
---|
351 | return true; |
---|
352 | |
---|
353 | ostringstream msg; |
---|
354 | msg << name << " - Received event has " << has << " bytes, but expected " << size << "."; |
---|
355 | T::Fatal(msg); |
---|
356 | return false; |
---|
357 | } |
---|
358 | |
---|
359 | int SetVerbosity(const EventImp &evt) |
---|
360 | { |
---|
361 | if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1)) |
---|
362 | return T::kSM_FatalError; |
---|
363 | |
---|
364 | fWeather.SetVerbose(evt.GetBool()); |
---|
365 | |
---|
366 | return T::GetCurrentState(); |
---|
367 | } |
---|
368 | /* |
---|
369 | int Disconnect() |
---|
370 | { |
---|
371 | // Close all connections |
---|
372 | fWeather.PostClose(false); |
---|
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 | ba::io_service::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 | return fWeather.GetState(); |
---|
398 | } |
---|
399 | |
---|
400 | public: |
---|
401 | StateMachineWeather(ostream &out=cout) : |
---|
402 | StateMachineAsio<T>(out, "MAGIC_WEATHER"), fWeather(*this, *this) |
---|
403 | { |
---|
404 | // State names |
---|
405 | T::AddStateName(State::kDisconnected, "NoConnection", |
---|
406 | "No connection to web-server could be established recently"); |
---|
407 | |
---|
408 | T::AddStateName(State::kConnected, "Invalid", |
---|
409 | "Connection to webserver can be established, but received data is not recent or invalid"); |
---|
410 | |
---|
411 | T::AddStateName(State::kReceiving, "Valid", |
---|
412 | "Connection to webserver can be established, receint data received"); |
---|
413 | |
---|
414 | // Verbosity commands |
---|
415 | T::AddEvent("SET_VERBOSE", "B") |
---|
416 | (bind(&StateMachineWeather::SetVerbosity, this, placeholders::_1)) |
---|
417 | ("set verbosity state" |
---|
418 | "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data"); |
---|
419 | /* |
---|
420 | // Conenction commands |
---|
421 | AddEvent("DISCONNECT") |
---|
422 | (bind(&StateMachineWeather::Disconnect, this)) |
---|
423 | ("disconnect from ethernet"); |
---|
424 | |
---|
425 | AddEvent("RECONNECT", "O") |
---|
426 | (bind(&StateMachineWeather::Reconnect, this, placeholders::_1)) |
---|
427 | ("(Re)connect ethernet connection to FTM, a new address can be given" |
---|
428 | "|[host][string]:new ethernet address in the form <host:port>"); |
---|
429 | */ |
---|
430 | } |
---|
431 | |
---|
432 | int EvalOptions(Configuration &conf) |
---|
433 | { |
---|
434 | fWeather.SetVerbose(!conf.Get<bool>("quiet")); |
---|
435 | fWeather.SetInterval(conf.Get<uint16_t>("interval")); |
---|
436 | fWeather.SetDebugTx(conf.Get<bool>("debug-tx")); |
---|
437 | fWeather.SetSite(conf.Get<string>("url")); |
---|
438 | fWeather.SetEndpoint(conf.Get<string>("addr")); |
---|
439 | fWeather.StartConnect(); |
---|
440 | |
---|
441 | return -1; |
---|
442 | } |
---|
443 | }; |
---|
444 | |
---|
445 | |
---|
446 | |
---|
447 | // ------------------------------------------------------------------------ |
---|
448 | |
---|
449 | #include "Main.h" |
---|
450 | |
---|
451 | |
---|
452 | template<class T, class S, class R> |
---|
453 | int RunShell(Configuration &conf) |
---|
454 | { |
---|
455 | return Main::execute<T, StateMachineWeather<S, R>>(conf); |
---|
456 | } |
---|
457 | |
---|
458 | void SetupConfiguration(Configuration &conf) |
---|
459 | { |
---|
460 | po::options_description control("MAGIC weather control options"); |
---|
461 | control.add_options() |
---|
462 | ("no-dim,d", po_switch(), "Disable dim services") |
---|
463 | ("addr,a", var<string>("www.magic.iac.es:80"), "Network address of Cosy") |
---|
464 | ("url,u", var<string>("/site/weather/weather_data.txt"), "File name and path to load") |
---|
465 | ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.") |
---|
466 | ("interval,i", var<uint16_t>(30), "Interval between two updates on the server in seconds") |
---|
467 | ("debug-tx", po_bool(), "Enable debugging of ethernet transmission.") |
---|
468 | ; |
---|
469 | |
---|
470 | conf.AddOptions(control); |
---|
471 | } |
---|
472 | |
---|
473 | /* |
---|
474 | Extract usage clause(s) [if any] for SYNOPSIS. |
---|
475 | Translators: "Usage" and "or" here are patterns (regular expressions) which |
---|
476 | are used to match the usage synopsis in program output. An example from cp |
---|
477 | (GNU coreutils) which contains both strings: |
---|
478 | Usage: cp [OPTION]... [-T] SOURCE DEST |
---|
479 | or: cp [OPTION]... SOURCE... DIRECTORY |
---|
480 | or: cp [OPTION]... -t DIRECTORY SOURCE... |
---|
481 | */ |
---|
482 | void PrintUsage() |
---|
483 | { |
---|
484 | cout << |
---|
485 | "The magicweather is an interface to the MAGIC weather data.\n" |
---|
486 | "\n" |
---|
487 | "The default is that the program is started without user intercation. " |
---|
488 | "All actions are supposed to arrive as DimCommands. Using the -c " |
---|
489 | "option, a local shell can be initialized. With h or help a short " |
---|
490 | "help message about the usuage can be brought to the screen.\n" |
---|
491 | "\n" |
---|
492 | "Usage: magicweather [-c type] [OPTIONS]\n" |
---|
493 | " or: magicweather [OPTIONS]\n"; |
---|
494 | cout << endl; |
---|
495 | } |
---|
496 | |
---|
497 | void PrintHelp() |
---|
498 | { |
---|
499 | // Main::PrintHelp<StateMachineFTM<StateMachine, ConnectionFTM>>(); |
---|
500 | |
---|
501 | /* Additional help text which is printed after the configuration |
---|
502 | options goes here */ |
---|
503 | |
---|
504 | /* |
---|
505 | cout << "bla bla bla" << endl << endl; |
---|
506 | cout << endl; |
---|
507 | cout << "Environment:" << endl; |
---|
508 | cout << "environment" << endl; |
---|
509 | cout << endl; |
---|
510 | cout << "Examples:" << endl; |
---|
511 | cout << "test exam" << endl; |
---|
512 | cout << endl; |
---|
513 | cout << "Files:" << endl; |
---|
514 | cout << "files" << endl; |
---|
515 | cout << endl; |
---|
516 | */ |
---|
517 | } |
---|
518 | |
---|
519 | int main(int argc, const char* argv[]) |
---|
520 | { |
---|
521 | Configuration conf(argv[0]); |
---|
522 | conf.SetPrintUsage(PrintUsage); |
---|
523 | Main::SetupConfiguration(conf); |
---|
524 | SetupConfiguration(conf); |
---|
525 | |
---|
526 | if (!conf.DoParse(argc, argv, PrintHelp)) |
---|
527 | return 127; |
---|
528 | |
---|
529 | //try |
---|
530 | { |
---|
531 | // No console access at all |
---|
532 | if (!conf.Has("console")) |
---|
533 | { |
---|
534 | if (conf.Get<bool>("no-dim")) |
---|
535 | return RunShell<LocalStream, StateMachine, ConnectionWeather>(conf); |
---|
536 | else |
---|
537 | return RunShell<LocalStream, StateMachineDim, ConnectionDimWeather>(conf); |
---|
538 | } |
---|
539 | // Cosole access w/ and w/o Dim |
---|
540 | if (conf.Get<bool>("no-dim")) |
---|
541 | { |
---|
542 | if (conf.Get<int>("console")==0) |
---|
543 | return RunShell<LocalShell, StateMachine, ConnectionWeather>(conf); |
---|
544 | else |
---|
545 | return RunShell<LocalConsole, StateMachine, ConnectionWeather>(conf); |
---|
546 | } |
---|
547 | else |
---|
548 | { |
---|
549 | if (conf.Get<int>("console")==0) |
---|
550 | return RunShell<LocalShell, StateMachineDim, ConnectionDimWeather>(conf); |
---|
551 | else |
---|
552 | return RunShell<LocalConsole, StateMachineDim, ConnectionDimWeather>(conf); |
---|
553 | } |
---|
554 | } |
---|
555 | /*catch (std::exception& e) |
---|
556 | { |
---|
557 | cerr << "Exception: " << e.what() << endl; |
---|
558 | return -1; |
---|
559 | }*/ |
---|
560 | |
---|
561 | return 0; |
---|
562 | } |
---|