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

Last change on this file since 18974 was 18974, checked in by tbretz, 21 months ago
Reaplced an 8-bit ascii charcter by its unicode representation.
File size: 16.0 KB
Line 
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
23namespace ba = boost::asio;
24namespace bs = boost::system;
25namespace dummy = ba::placeholders;
26
27using namespace std;
28using namespace MagicWeather;
29
30// ------------------------------------------------------------------------
31
32class 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
44protected:
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
258private:
259    // This is called when a connection was established
260    void ConnectionEstablished()
261    {
262        Request();
263        StartReadReport();
264    }
265
266public:
267
268    static const uint16_t kMaxAddr;
269
270public:
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
306const uint16_t ConnectionWeather::kMaxAddr = 0xfff;
307
308// ------------------------------------------------------------------------
309
310#include "DimDescriptionService.h"
311
312class ConnectionDimWeather : 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    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
342template <class T, class S>
343class StateMachineWeather : public StateMachineAsio<T>
344{
345private:
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
400public:
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
452template<class T, class S, class R>
453int RunShell(Configuration &conf)
454{
455    return Main::execute<T, StateMachineWeather<S, R>>(conf);
456}
457
458void 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 */
482void 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
497void 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
519int 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}
Note: See TracBrowser for help on using the repository browser.