source: trunk/FACT++/src/gcn.cc @ 19467

Last change on this file since 19467 was 19467, checked in by tbretz, 8 weeks ago
The Dim Command was not up-to-date.
File size: 23.3 KB
Line 
1#include <functional>
2#include <boost/algorithm/string/join.hpp>
3
4#include "Dim.h"
5#include "Event.h"
6#include "Shell.h"
7#include "StateMachineDim.h"
8#include "StateMachineAsio.h"
9#include "Connection.h"
10#include "LocalControl.h"
11#include "Configuration.h"
12#include "Console.h"
13#include "Converter.h"
14
15#include "tools.h"
16#include "../externals/nova.h"
17
18#include "HeadersGCN.h"
19#include "HeadersToO.h"
20
21#include <QtXml/QDomDocument>
22
23namespace ba    = boost::asio;
24namespace bs    = boost::system;
25namespace dummy = ba::placeholders;
26
27using namespace std;
28using namespace GCN;
29
30// ------------------------------------------------------------------------
31
32class ConnectionGCN : public Connection
33{
34private:
35    map<uint16_t, GCN::PaketType_t> fTypes;
36
37    vector<string> fEndPoints;
38    int fEndPoint;
39
40    bool fIsVerbose;
41    bool fDebugRx;
42
43    uint32_t     fRxSize;
44    vector<char> fRxData;
45
46    Time fLastKeepAlive;
47
48    QString GetParamValue(const QDomElement &what, const string &name)
49    {
50        const QDomNodeList param = what.elementsByTagName("Param");
51        for (int i=0; i<param.count(); i++)
52        {
53            const QDomElement elem = param.at(i).toElement();
54            if (elem.attribute("name").toStdString()==name)
55                return elem.attribute("value");
56        }
57
58        return "";
59    }
60
61    GCN::PaketType_t GetType(const QDomElement &what)
62    {
63        const auto value = GetParamValue(what, "Packet_Type");
64        if (value.isEmpty())
65            return { -1, "", "" };
66
67        const uint16_t val = value.toUInt();
68        const auto it = fTypes.find(val);
69        if (it!=fTypes.end())
70            return it->second;
71
72        Warn("Unknown paket type "+to_string(val)+".");
73        return { -1, "", "" };
74    }
75
76    int ProcessXml(const QDomElement &root)
77    {
78        if (root.isNull())
79            return -1;
80
81        const string role = root.attribute("role", "").toStdString();
82        const string name = root.tagName().toStdString();
83
84        // A full description can be found at http://voevent.dc3.com/schema/default.html
85
86        if (name=="trn:Transport")
87        {
88            if (role=="iamalive")
89            {
90                const QDomElement orig = root.firstChildElement("Origin");
91                const QDomElement time = root.firstChildElement("TimeStamp");
92                if (orig.isNull() || time.isNull())
93                    return -1;
94
95                fLastKeepAlive = Time(time.text().toStdString());
96
97                if (fIsVerbose)
98                {
99                    Out() << Time().GetAsStr() << " ----- " << name << " [" << role << "] -----" << endl;
100                    Out() << " " << time.tagName().toStdString() << " = " << fLastKeepAlive.GetAsStr() << '\n';
101                    Out() << " " << orig.tagName().toStdString() << " = " << orig.text().toStdString() << '\n';
102                    Out() << endl;
103                }
104
105                return true;
106            }
107
108            return false;
109        }
110
111        ofstream fout("gcn.stream", ios::app);
112        fout << "------------------------------------------------------------------------------\n" << fRxData.data() << endl;
113
114        if (name=="voe:VOEvent")
115        {
116            // WHAT: http://gcn.gsfc.nasa.gov/tech_describe.html
117            const QDomElement who  = root.firstChildElement("Who");
118            const QDomElement what = root.firstChildElement("What");
119            const QDomElement when = root.firstChildElement("WhereWhen");
120            //const QDomElement how  = root.firstChildElement("How");
121            //const QDomElement why  = root.firstChildElement("Why");
122            //const QDomElement cite = root.firstChildElement("Citations");
123            //const QDomElement desc = root.firstChildElement("Description");
124            //const QDomElement ref  = root.firstChildElement("Reference");
125            if (who.isNull() || what.isNull() || when.isNull())
126                return -1;
127
128            const GCN::PaketType_t ptype = GetType(what);
129
130            const QDomElement date   = who.firstChildElement("Date");
131            const QDomElement author = who.firstChildElement("Author");
132            const QDomElement sname  = author.firstChildElement("shortName");
133            const QDomElement desc   = what.firstChildElement("Description");
134
135            const QDomElement obsdat = when.firstChildElement("ObsDataLocation");
136            const QDomElement obsloc = obsdat.firstChildElement("ObservationLocation");
137            const QDomElement coord  = obsloc.firstChildElement("AstroCoords");
138
139            const QDomElement time   = coord.firstChildElement("Time").firstChildElement("TimeInstant").firstChildElement("ISOTime");
140            const QDomElement pos2d  = coord.firstChildElement("Position2D");
141            const QDomElement name1  = pos2d.firstChildElement("Name1");
142            const QDomElement name2  = pos2d.firstChildElement("Name2");
143            const QDomElement val2   = pos2d.firstChildElement("Value2");
144            const QDomElement c1     = val2.firstChildElement("C1");
145            const QDomElement c2     = val2.firstChildElement("C2");
146            const QDomElement errad  = pos2d.firstChildElement("Error2Radius");
147
148            const bool is_gw = ptype.type==150 || ptype.type==151 || ptype.type==153;
149
150            vector<string> missing;
151            if (date.isNull())
152                missing.emplace_back("Date");
153            if (author.isNull())
154                missing.emplace_back("Author");
155            if (sname.isNull() && !is_gw)
156                missing.emplace_back("shortName");
157            if (obsdat.isNull())
158                missing.emplace_back("ObsDataLocation");
159            if (obsloc.isNull())
160                missing.emplace_back("ObservationLocation");
161            if (coord.isNull())
162                missing.emplace_back("AstroCoords");
163            if (time.isNull())
164                missing.emplace_back("Time/TimeInstant/ISOTime");
165            if (pos2d.isNull() && !is_gw)
166                missing.emplace_back("Position2D");
167            if (name1.isNull() && !is_gw)
168                missing.emplace_back("Name1");
169            if (name1.isNull() && !is_gw)
170                missing.emplace_back("Name2");
171            if (val2.isNull() && !is_gw)
172                missing.emplace_back("Value2");
173            if (c1.isNull() && !is_gw)
174                missing.emplace_back("C1");
175            if (c2.isNull() && !is_gw)
176                missing.emplace_back("C2");
177            if (errad.isNull() && !is_gw)
178                missing.emplace_back("Error2Radius");
179
180            if (!missing.empty())
181            {
182                Warn("Missing elements: "+boost::algorithm::join(missing, ", "));
183                return -1;
184            }
185
186            //  59/31: Konus LC / IPN raw         [observation]
187            //   110:  Fermi GBM (ART)            [observation]  (Initial)       // Stop data taking
188            //   111:  Fermi GBM (FLT)            [observation]  (after ~2s)     // Start pointing/run
189            //   112:  Fermi GBM (GND)            [observation]  (after 2-20s)   // Refine pointing
190            //   115:  Fermi GBM position         [observation]  (final ~hours)
191            //
192            //    51:  Intergal pointdir              [utility]
193            //    83:  Swift pointdir                 [utility]
194            //   129:  Fermi pointdir                 [utility]
195            //
196            //     2:  Test coord             (      1)  [test]
197            //    44:  HETE test              ( 41- 43)  [test]
198            //    52:  Integral SPIACS                   [test]
199            //    53:  Integral Wakeup                   [test]
200            //    54:  Integral refined                  [test]
201            //    55:  Integral Offline                  [test]
202            //    56:  Integral Weak                     [test]
203            //    82:  BAT   GRB pos test     (     61)  [test]
204            //   109:  AGILE GRB pos test     (100-103)  [test]
205            //   119:  Fermi GRB pos test     (111-113)  [test]
206            //   124:  Fermi LAT pos upd test (120-122)  [test]
207            //   136:  MAXI coord test        (    134)  [test]
208            //
209            // Integral: RA=1.2343, Dec=2.3456
210            //
211
212            /*
213             54
214             ==
215             <Group name="Test_mpos" >
216               <Param name="Test_Notice"              value="true" />
217             </Group>
218
219
220             82
221             ==
222             <Group name="Solution_Status" >
223               <Param name="Test_Submission"          value="false" />
224             </Group>
225
226
227             115
228             ===
229             2013-07-20 19:04:13: TIME = 2013-07-20 02:46:40
230
231             <Group name="Trigger_ID" >
232               <Param name="Test_Submission"       value="false" />
233             </Group>
234             */
235
236            const string unit = pos2d.attribute("unit").toStdString();
237
238            const uint32_t trig  = GetParamValue(what, "TrigID").toUInt();
239            const double   ra    = c1.text().toDouble();
240            const double   dec   = c2.text().toDouble();
241            const double   err   = errad.text().toDouble();
242
243            const string n1 = name1.text().toStdString();
244            const string n2 = name2.text().toStdString();
245
246            Out() << Time(date.text().toStdString()).GetAsStr() << " ----- " << sname.text().toStdString() << " [" << role << "]\n";
247            if (!desc.isNull())
248                Out() << "[" << desc.text().toStdString()  << "]\n";
249            Out() << ptype.name << "[" << ptype.type << "]: " << ptype.description << endl;
250            Out() << left;
251            Out() << "  " << setw(5) << "TIME" << "= " << Time(time.text().toStdString()).GetAsStr() << '\n';
252            Out() << "  " << setw(5) << n1     << "= " << ra  << unit << '\n';
253            Out() << "  " << setw(5) << n2     << "= " << dec << unit << '\n';
254            Out() << "  " << setw(5) << "ERR"  << "= " << err << unit << '\n';
255
256            const bool has_coordinates = n1=="RA" && n2=="Dec" && unit=="deg";
257
258            if (has_coordinates)
259            {
260                const ToO::DataGRB data =
261                {
262                    .type   = ptype.type,
263                    .trigid = trig,
264                    .ra     = ra,
265                    .dec    = dec,
266                    .err    = err,
267                };
268
269                Info("Sending ToO #"+to_string(trig)+" ["+role+"]");
270                Dim::SendCommandNB("SCHEDULER/GCN", data);
271
272/*
273                const double jd = Time().JD();
274
275                Nova::EquPosn equ;
276                equ.ra  = ra;
277                equ.dec = dec;
278
279                const Nova::ZdAzPosn pos = Nova::GetHrzFromEqu(equ, jd);
280                const Nova::EquPosn moon = Nova::GetLunarEquCoords(jd);
281                const Nova::ZdAzPosn sun = Nova::GetHrzFromEqu(Nova::GetSolarEquCoords(jd), jd);
282
283                const double disk = Nova::GetLunarDisk(jd);
284                const double dist = Nova::GetAngularSeparation(equ, moon);
285
286                Out() << "  " << setw(5) << "ZD"   << "= " << pos.zd << "deg\n";
287                Out() << "  " << setw(5) << "Az"   << "= " << pos.az << "deg\n";
288
289                Out() << "  " << setw(5) << "MOON" << "= " << int(disk*100) << "%\n";
290                Out() << "  " << setw(5) << "DIST" << "= " << dist << "deg\n";
291
292                if (dist>10 && dist<170 && pos.zd<80 && sun.zd>108)
293                {
294                    Out() << "  visible ";
295                    if (pos.zd<70)
296                        Out() << '+';
297                    if (pos.zd<60)
298                        Out() << '+';
299                    if (pos.zd<45)
300                        Out() << '+';
301                    Out() << '\n';
302                }
303*/
304            }
305
306            Out() << endl;
307
308            if (role=="observation")
309            {
310                return true;
311            }
312
313            if (role=="test")
314            {
315                return true;
316            }
317
318            if (role=="retraction")
319            {
320                return true;
321            }
322
323            if (role=="utility")
324            {
325                return true;
326            }
327
328            return false;
329        }
330
331        Out() << Time().GetAsStr() << " ----- " << name << " [" << role << "] -----" << endl;
332
333        return false;
334    }
335
336    void HandleReceivedData(const bs::error_code& err, size_t bytes_received, int type)
337    {
338        // Do not schedule a new read if the connection failed.
339        if (bytes_received==0 || err)
340        {
341            if (err==ba::error::eof)
342                Warn("Connection closed by remote host (GCN).");
343
344            // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category))
345            // 125: Operation canceled
346            if (err && err!=ba::error::eof &&                     // Connection closed by remote host
347                err!=ba::error::basic_errors::not_connected &&    // Connection closed by remote host
348                err!=ba::error::basic_errors::operation_aborted)  // Connection closed by us
349            {
350                ostringstream str;
351                str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl;
352                Error(str);
353            }
354            PostClose(err!=ba::error::basic_errors::operation_aborted);
355            return;
356        }
357
358        if (type==0)
359        {
360            fRxSize = ntohl(fRxSize);
361            fRxData.assign(fRxSize+1, 0);
362            ba::async_read(*this, ba::buffer(fRxData, fRxSize),
363                             boost::bind(&ConnectionGCN::HandleReceivedData, this,
364                                         dummy::error, dummy::bytes_transferred, 1));
365            return;
366        }
367
368        if (fDebugRx)
369        {
370            Out() << "------------------------------------------------------\n";
371            Out() << fRxData.data() << '\n';
372            Out() << "------------------------------------------------------" << endl;
373        }
374
375        QDomDocument doc;
376        if (!doc.setContent(QString(fRxData.data()), false))
377        {
378            Warn("Parsing of xml failed [0].");
379            Out() << "------------------------------------------------------\n";
380            Out() << fRxData.data() << '\n';
381            Out() << "------------------------------------------------------" << endl;
382            PostClose(false);
383            return;
384        }
385
386        if (fDebugRx)
387            Out() << "Parsed:\n-------\n" << doc.toString().toStdString() << endl;
388
389        const int rc = ProcessXml(doc.documentElement());
390        if (rc<0)
391        {
392            Warn("Parsing of xml failed [1].");
393            Out() << "------------------------------------------------------\n";
394            Out() << fRxData.data() << '\n';
395            Out() << "------------------------------------------------------" << endl;
396            PostClose(false);
397            return;
398        }
399
400        if (!rc)
401        {
402            Out() << "------------------------------------------------------\n";
403            Out() << doc.toString().toStdString() << '\n';
404            Out() << "------------------------------------------------------" << endl;
405        }
406
407        StartRead();
408    }
409
410    void StartRead()
411    {
412        ba::async_read(*this, ba::buffer(&fRxSize, 4),
413                       boost::bind(&ConnectionGCN::HandleReceivedData, this,
414                                   dummy::error, dummy::bytes_transferred, 0));
415    }
416
417    // This is called when a connection was established
418    void ConnectionEstablished()
419    {
420        StartRead();
421    }
422
423public:
424    ConnectionGCN(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()),
425        fIsVerbose(false), fDebugRx(false), fLastKeepAlive(Time::none)
426    {
427        SetLogStream(&imp);
428
429        for (auto it=GCN::kTypes; it->type>0; it++)
430            fTypes[it->type] = *it;
431    }
432
433    void SetVerbose(bool b)
434    {
435        fIsVerbose = b;
436    }
437
438    void SetDebugRx(bool b)
439    {
440        fDebugRx = b;
441        Connection::SetVerbose(b);
442    }
443
444    void SetEndPoints(const vector<string> &v)
445    {
446        fEndPoints = v;
447        fEndPoint = 0;
448    }
449
450    void StartConnect()
451    {
452        if (fEndPoints.size()>0)
453            SetEndpoint(fEndPoints[fEndPoint++%fEndPoints.size()]);
454        Connection::StartConnect();
455    }
456
457    bool IsValid()
458    {
459        return fLastKeepAlive.IsValid() ? Time()-fLastKeepAlive<boost::posix_time::minutes(2) : false;
460    }
461};
462
463// ------------------------------------------------------------------------
464
465#include "DimDescriptionService.h"
466
467class ConnectionDimGCN : public ConnectionGCN
468{
469private:
470
471public:
472    ConnectionDimGCN(ba::io_service& ioservice, MessageImp &imp) : ConnectionGCN(ioservice, imp)
473    {
474    }
475};
476
477// ------------------------------------------------------------------------
478
479template <class T, class S>
480class StateMachineGCN : public StateMachineAsio<T>
481{
482private:
483    S fGCN;
484
485    int Disconnect()
486    {
487        // Close all connections
488        fGCN.PostClose(false);
489
490        return T::GetCurrentState();
491    }
492
493    int Reconnect(const EventImp &evt)
494    {
495        // Close all connections to supress the warning in SetEndpoint
496        fGCN.PostClose(false);
497
498        // Now wait until all connection have been closed and
499        // all pending handlers have been processed
500        ba::io_service::poll();
501
502        if (evt.GetBool())
503            fGCN.SetEndpoint(evt.GetString());
504
505        // Now we can reopen the connection
506        fGCN.PostClose(true);
507
508        return T::GetCurrentState();
509    }
510
511    int Execute()
512    {
513        if (!fGCN.IsConnected())
514            return State::kDisconnected;
515
516        return fGCN.IsValid() ? State::kValid : State::kConnected;
517    }
518
519    bool CheckEventSize(size_t has, const char *name, size_t size)
520    {
521        if (has==size)
522            return true;
523
524        ostringstream msg;
525        msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
526        T::Fatal(msg);
527        return false;
528    }
529
530    int SetVerbosity(const EventImp &evt)
531    {
532        if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1))
533            return T::kSM_FatalError;
534
535        fGCN.SetVerbose(evt.GetBool());
536
537        return T::GetCurrentState();
538    }
539
540    int SetDebugRx(const EventImp &evt)
541    {
542        if (!CheckEventSize(evt.GetSize(), "SetDebugRx", 1))
543            return T::kSM_FatalError;
544
545        fGCN.SetDebugRx(evt.GetBool());
546
547        return T::GetCurrentState();
548    }
549
550public:
551    StateMachineGCN(ostream &out=cout) :
552        StateMachineAsio<T>(out, "GCN"), fGCN(*this, *this)
553    {
554        // State names
555        T::AddStateName(State::kDisconnected, "Disconnected",
556                     "No connection to GCN.");
557        T::AddStateName(State::kConnected, "Connected",
558                     "Connection to GCN established.");
559        T::AddStateName(State::kValid, "Valid",
560                     "Connection valid (keep alive received within past 2min)");
561
562        // Verbosity commands
563        T::AddEvent("SET_VERBOSE", "B:1")
564            (bind(&StateMachineGCN::SetVerbosity, this, placeholders::_1))
565            ("set verbosity state"
566             "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
567        T::AddEvent("SET_DEBUG_RX", "B:1")
568            (bind(&StateMachineGCN::SetDebugRx, this, placeholders::_1))
569            ("Set debux-rx state"
570             "|debug[bool]:dump received text and parsed text to console (yes/no)");
571
572
573        // Conenction commands
574        T::AddEvent("DISCONNECT", State::kConnected)
575            (bind(&StateMachineGCN::Disconnect, this))
576            ("disconnect from ethernet");
577        T::AddEvent("RECONNECT", "O", State::kDisconnected, State::kConnected)
578            (bind(&StateMachineGCN::Reconnect, this, placeholders::_1))
579            ("(Re)connect ethernet connection to FTM, a new address can be given"
580             "|[host][string]:new ethernet address in the form <host:port>");
581
582        fGCN.StartConnect();
583    }
584
585    void SetEndpoint(const string &url)
586    {
587        vector<string> v;
588        v.push_back(url);
589        fGCN.SetEndPoints(v);
590    }
591
592    vector<string> fEndPoints;
593
594    int EvalOptions(Configuration &conf)
595    {
596        fGCN.SetVerbose(!conf.Get<bool>("quiet"));
597        fGCN.SetEndPoints(conf.Vec<string>("addr"));
598
599        return -1;
600    }
601};
602
603// ------------------------------------------------------------------------
604
605#include "Main.h"
606
607template<class T, class S, class R>
608int RunShell(Configuration &conf)
609{
610    return Main::execute<T, StateMachineGCN<S, R>>(conf);
611}
612
613void SetupConfiguration(Configuration &conf)
614{
615    po::options_description control("FTM control options");
616    control.add_options()
617        ("no-dim",        po_bool(),  "Disable dim services")
618        ("addr,a",        vars<string>(),  "Network addresses of GCN server")
619        ("quiet,q",       po_bool(true),  "Disable printing contents of all received messages (except dynamic data) in clear text.")
620        ;
621
622    conf.AddOptions(control);
623}
624
625/*
626 Extract usage clause(s) [if any] for SYNOPSIS.
627 Translators: "Usage" and "or" here are patterns (regular expressions) which
628 are used to match the usage synopsis in program output.  An example from cp
629 (GNU coreutils) which contains both strings:
630  Usage: cp [OPTION]... [-T] SOURCE DEST
631    or:  cp [OPTION]... SOURCE... DIRECTORY
632    or:  cp [OPTION]... -t DIRECTORY SOURCE...
633 */
634void PrintUsage()
635{
636    cout <<
637        "The gcn reads and evaluates alerts from the GCN network.\n"
638        "\n"
639        "The default is that the program is started without user intercation. "
640        "All actions are supposed to arrive as DimCommands. Using the -c "
641        "option, a local shell can be initialized. With h or help a short "
642        "help message about the usuage can be brought to the screen.\n"
643        "\n"
644        "Usage: gcn [-c type] [OPTIONS]\n"
645        "  or:  gcn [OPTIONS]\n";
646    cout << endl;
647}
648
649void PrintHelp()
650{
651    Main::PrintHelp<StateMachineGCN<StateMachine, ConnectionGCN>>();
652
653    /* Additional help text which is printed after the configuration
654     options goes here */
655
656    /*
657     cout << "bla bla bla" << endl << endl;
658     cout << endl;
659     cout << "Environment:" << endl;
660     cout << "environment" << endl;
661     cout << endl;
662     cout << "Examples:" << endl;
663     cout << "test exam" << endl;
664     cout << endl;
665     cout << "Files:" << endl;
666     cout << "files" << endl;
667     cout << endl;
668     */
669}
670
671int main(int argc, const char* argv[])
672{
673    Configuration conf(argv[0]);
674    conf.SetPrintUsage(PrintUsage);
675    Main::SetupConfiguration(conf);
676    SetupConfiguration(conf);
677
678    if (!conf.DoParse(argc, argv, PrintHelp))
679        return 127;
680
681    //try
682    {
683        // No console access at all
684        if (!conf.Has("console"))
685        {
686            if (conf.Get<bool>("no-dim"))
687                return RunShell<LocalStream, StateMachine, ConnectionGCN>(conf);
688            else
689                return RunShell<LocalStream, StateMachineDim, ConnectionDimGCN>(conf);
690        }
691        // Cosole access w/ and w/o Dim
692        if (conf.Get<bool>("no-dim"))
693        {
694            if (conf.Get<int>("console")==0)
695                return RunShell<LocalShell, StateMachine, ConnectionGCN>(conf);
696            else
697                return RunShell<LocalConsole, StateMachine, ConnectionGCN>(conf);
698        }
699        else
700        {
701            if (conf.Get<int>("console")==0)
702                return RunShell<LocalShell, StateMachineDim, ConnectionDimGCN>(conf);
703            else
704                return RunShell<LocalConsole, StateMachineDim, ConnectionDimGCN>(conf);
705        }
706    }
707    /*catch (std::exception& e)
708    {
709        cerr << "Exception: " << e.what() << endl;
710        return -1;
711    }*/
712
713    return 0;
714}
Note: See TracBrowser for help on using the repository browser.