#include #include #include "Database.h" #include "Dim.h" #include "Event.h" #include "Shell.h" #include "StateMachineDim.h" #include "StateMachineAsio.h" #include "Connection.h" #include "LocalControl.h" #include "Configuration.h" #include "Console.h" #include "Converter.h" #include "tools.h" #include "../externals/nova.h" #include "HeadersGCN.h" #include "HeadersToO.h" #include namespace ba = boost::asio; namespace bs = boost::system; namespace dummy = ba::placeholders; using namespace std; using namespace GCN; // ------------------------------------------------------------------------ class ConnectionGCN : public Connection { private: map fTypes; vector fEndPoints; int fEndPoint; bool fIsVerbose; bool fDebugRx; string fUri; uint32_t fRxSize; vector fRxData; Time fLastKeepAlive; QString GetParamValue(const QDomElement &what, const string &name) { const QDomNodeList param = what.elementsByTagName("Param"); for (int i=0; ifirst; if (id==0) { Warn("Packet ID = 0"); return -252; } // Gravitational wave event const bool is_gw = id==150 || id==151 || id==152 || id==153 || id==164; // Required keywords vector missing; if (date.isNull()) missing.emplace_back("Date"); if (author.isNull()) missing.emplace_back("Author"); if (sname.isNull() && !is_gw) missing.emplace_back("shortName"); if (obsdat.isNull()) missing.emplace_back("ObsDataLocation"); if (obsloc.isNull()) missing.emplace_back("ObservationLocation"); if (coord.isNull()) missing.emplace_back("AstroCoords"); if (time.isNull()) missing.emplace_back("Time/TimeInstant/ISOTime"); if (pos2d.isNull() && !is_gw) missing.emplace_back("Position2D"); if (name1.isNull() && !is_gw) missing.emplace_back("Name1"); if (name1.isNull() && !is_gw) missing.emplace_back("Name2"); if (val2.isNull() && !is_gw) missing.emplace_back("Value2"); if (c1.isNull() && !is_gw) missing.emplace_back("C1"); if (c2.isNull() && !is_gw) missing.emplace_back("C2"); if (errad.isNull() && !is_gw) missing.emplace_back("Error2Radius"); if (!missing.empty()) { Warn("Missing elements: "+boost::algorithm::join(missing, ", ")); return -id; } // 59/31: Konus LC / IPN raw [observation] // 110: Fermi GBM (ART) [observation] (Initial) // Stop data taking // 111: Fermi GBM (FLT) [observation] (after ~2s) // Start pointing/run // 112: Fermi GBM (GND) [observation] (after 2-20s) // Refine pointing // 115: Fermi GBM position [observation] (final ~hours) // // 51: Intergal pointdir [utility] // 83: Swift pointdir [utility] // 129: Fermi pointdir [utility] // // 2: Test coord ( 1) [test] // 44: HETE test ( 41- 43) [test] // 52: Integral SPIACS [test] // 53: Integral Wakeup [test] // 54: Integral refined [test] // 55: Integral Offline [test] // 56: Integral Weak [test] // 82: BAT GRB pos test ( 61) [test] // 109: AGILE GRB pos test (100-103) [test] // 119: Fermi GRB pos test (111-113) [test] // 124: Fermi LAT pos upd test (120-122) [test] // 136: MAXI coord test ( 134) [test] // // Integral: RA=1.2343, Dec=2.3456 // /* 54 == 82 == 115 === 2013-07-20 19:04:13: TIME = 2013-07-20 02:46:40 */ // ----------------------------- Create Name ---------------------- const auto &paket = ptype->second; string name; bool prefix = true; switch (id) { // case 60: // case 61: // case 62: // // case 110: // case 111: // case 112: // case 115: // name = GetParamValue(what, "TRIGGER_NUM").toStdString(); // break; // // case 123: // name = GetParamValue(what, "REF_NUM").toStdString(); // break; case 125: name = GetParamValue(what, "SourceName").toStdString(); prefix = false; break; case 140: name = GetParamValue(what, "TrigID").toStdString(); if (name[0]=='-') name.erase(name.begin()); break; case 157: case 158: { const string event_id = GetParamValue(what, "event_id").toStdString(); const string run_id = GetParamValue(what, "run_id").toStdString(); name = event_id+"_"+run_id; break; } case 171: case 173: case 174: { const string run_id = GetParamValue(what, "run_id").toStdString(); const string event_id = GetParamValue(what, "event_id").toStdString(); name = run_id+"_"+event_id; break; } // Missing ID // case 51: // case 83: // case 119: // case 129: default: name = GetParamValue(what, "TrigID").toStdString(); } if (name.empty() || name=="0") { Warn("Missing ID... cannot create default source name... using date instead."); name = Time().GetAsStr("%Y%m%d_%H%M%S"); prefix = true; } if (prefix) name.insert(0, paket.instrument+"#"); // ---------------------------------------------------------------- const string unit = pos2d.attribute("unit").toStdString(); const double ra = c1.text().toDouble(); const double dec = c2.text().toDouble(); const double err = errad.text().toDouble(); const string n1 = name1.text().toStdString(); const string n2 = name2.text().toStdString(); const bool has_coordinates = n1=="RA" && n2=="Dec" && unit=="deg" && ra!=0 && dec!=0; const std::set typelist = { 51, // INTEGRAL_POINTDIR 53, // INTEGRAL_WAKEUP 54, // INTEGRAL_REFINED 55, // INTEGRAL_OFFLINE // 56, // INTEGRAL_WEAK // 59, // KONUS_LC 60, // SWIFT_BAT_GRB_ALERT 61, // SWIFT_BAT_GRB_POS_ACK 62, // SWIFT_BAT_GRB_POS_NACK 83, // SWIFT_POINTDIR 97, // SWIFT_BAT_QL_POS 100, // AGILE_GRB_WAKEUP 101, // AGILE_GRB_GROUND 102, // AGILE_GRB_REFINED // 110, // FERMI_GBM_ALERT [0/0] 111, // FERMI_GBM_FLT_POS 112, // FERMI_GBM_GND_POS 115, // FERMI_GBM_FIN_POS 123, // FERMI_LAT_TRANS 125, // FERMI_LAT_MONITOR // 134, // MAXI_UNKNOWN // 135, // MAXI_KNOWN // 136, // MAXI_TEST 157, // AMON_ICECUBE_COINC 158, // AMON_ICECUBE_HESE 169, // AMON_ICECUBE_EHE 171, // HAWC_BURST_MONITOR 173, // ICECUBE_GOLD 174, // ICECUBE_BRONZE }; const bool integral_test = role=="test" && id>=53 && id<=56; const bool valid_id = typelist.find(id)!=typelist.end(); if (valid_id && has_coordinates && !integral_test) { const ToO::DataGRB data = { .type = id, .ra = ra, .dec = dec, .err = err, }; vector dim(sizeof(ToO::DataGRB) + name.size() + 1); memcpy(dim.data(), &data, sizeof(ToO::DataGRB)); memcpy(dim.data()+sizeof(ToO::DataGRB), name.c_str(), name.size()); Dim::SendCommandNB("SCHEDULER/GCN", dim); Info("Sent ToO '"+name+"' ["+role+"]"); } Out() << Time(date.text().toStdString()).GetAsStr() << " ----- " << sname.text().toStdString() << " [" << role << "]\n"; if (!desc.isNull()) Out() << "[" << desc.text().toStdString() << "]\n"; Out() << name << " [" << paket.name << ":" << id << "]: " << paket.description << endl; Out() << left; Out() << " " << setw(5) << "TIME" << "= " << Time(time.text().toStdString()).GetAsStr() << '\n'; Out() << " " << setw(5) << n1 << "= " << ra << unit << '\n'; Out() << " " << setw(5) << n2 << "= " << dec << unit << '\n'; Out() << " " << setw(5) << "ERR" << "= " << err << unit << '\n'; Out() << endl; if (role=="observation") { return true; } if (role=="test") { return true; } if (role=="retraction") { return true; } if (role=="utility") { return true; } return false; } Out() << Time().GetAsStr() << " ----- " << trn << " [" << role << "] -----" << endl; return false; } void HandleReceivedData(const bs::error_code& err, size_t bytes_received, int type) { // Do not schedule a new read if the connection failed. if (bytes_received==0 || err) { if (err==ba::error::eof) Warn("Connection closed by remote host (GCN)."); // 107: Transport endpoint is not connected (bs::error_code(107, bs::system_category)) // 125: Operation canceled if (err && err!=ba::error::eof && // Connection closed by remote host err!=ba::error::basic_errors::not_connected && // Connection closed by remote host err!=ba::error::basic_errors::operation_aborted) // Connection closed by us { ostringstream str; str << "Reading from " << URL() << ": " << err.message() << " (" << err << ")";// << endl; Error(str); } PostClose(err!=ba::error::basic_errors::operation_aborted); return; } if (type==0) { fRxSize = ntohl(fRxSize); fRxData.assign(fRxSize+1, 0); ba::async_read(*this, ba::buffer(fRxData, fRxSize), boost::bind(&ConnectionGCN::HandleReceivedData, this, dummy::error, dummy::bytes_transferred, 1)); return; } if (fDebugRx) { Out() << "------------------------------------------------------\n"; Out() << fRxData.data() << '\n'; Out() << "------------------------------------------------------" << endl; } QDomDocument doc; if (!doc.setContent(QString(fRxData.data()), false)) { Warn("Parsing of xml failed [0]."); Out() << "------------------------------------------------------\n"; Out() << fRxData.data() << '\n'; Out() << "------------------------------------------------------" << endl; PostClose(true); return; } if (fDebugRx) Out() << "Parsed:\n-------\n" << doc.toString().toStdString() << endl; const int rc = ProcessXml(doc.documentElement()); if (rc<0) { Warn("Parsing of xml failed [1]."); Out() << "------------------------------------------------------\n"; Out() << fRxData.data() << '\n'; Out() << "------------------------------------------------------" << endl; //PostClose(false); try { auto db = Database(fUri); // Make an entry in the alter databse to issue a call const string query = "INSERT FlareAlerts.FlareTriggers SET\n" " fTriggerInserted=Now(),\n" " fNight="+to_string(Time().NightAsInt())+",\n" " fPacketTypeKey="+to_string(-rc)+",\n" " fTriggerType=8"; auto q = db.query(query); q.execute(); if (!q.info().empty()) Info(q.info()); } catch (const mysqlpp::ConnectionFailed &e) { Error(e.what()); } } if (!rc) { Out() << "------------------------------------------------------\n"; Out() << doc.toString().toStdString() << '\n'; Out() << "------------------------------------------------------" << endl; } StartRead(); } void StartRead() { ba::async_read(*this, ba::buffer(&fRxSize, 4), boost::bind(&ConnectionGCN::HandleReceivedData, this, dummy::error, dummy::bytes_transferred, 0)); } // This is called when a connection was established void ConnectionEstablished() { StartRead(); } public: ConnectionGCN(ba::io_service& ioservice, MessageImp &imp) : Connection(ioservice, imp()), fIsVerbose(false), fDebugRx(false), fLastKeepAlive(Time::none) { SetLogStream(&imp); } void SetVerbose(bool b) { fIsVerbose = b; } void SetDebugRx(bool b) { fDebugRx = b; Connection::SetVerbose(b); } void SetURI(const string &uri) { fUri = uri; } void SetEndPoints(const vector &v) { fEndPoints = v; fEndPoint = 0; } void StartConnect() { if (fEndPoints.size()>0) SetEndpoint(fEndPoints[fEndPoint++%fEndPoints.size()]); Connection::StartConnect(); } bool IsValid() { return fLastKeepAlive.IsValid() ? Time()-fLastKeepAlive class StateMachineGCN : public StateMachineAsio { private: S fGCN; int Disconnect() { // Close all connections fGCN.PostClose(false); return T::GetCurrentState(); } int Reconnect(const EventImp &evt) { // Close all connections to supress the warning in SetEndpoint fGCN.PostClose(false); // Now wait until all connection have been closed and // all pending handlers have been processed ba::io_service::poll(); if (evt.GetBool()) fGCN.SetEndpoint(evt.GetString()); // Now we can reopen the connection fGCN.PostClose(true); return T::GetCurrentState(); } int Execute() { if (!fGCN.IsConnected()) return State::kDisconnected; return fGCN.IsValid() ? State::kValid : State::kConnected; } bool CheckEventSize(size_t has, const char *name, size_t size) { if (has==size) return true; ostringstream msg; msg << name << " - Received event has " << has << " bytes, but expected " << size << "."; T::Fatal(msg); return false; } int SetVerbosity(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "SetVerbosity", 1)) return T::kSM_FatalError; fGCN.SetVerbose(evt.GetBool()); return T::GetCurrentState(); } int SetDebugRx(const EventImp &evt) { if (!CheckEventSize(evt.GetSize(), "SetDebugRx", 1)) return T::kSM_FatalError; fGCN.SetDebugRx(evt.GetBool()); return T::GetCurrentState(); } public: StateMachineGCN(ostream &out=cout) : StateMachineAsio(out, "GCN"), fGCN(*this, *this) { // State names T::AddStateName(State::kDisconnected, "Disconnected", "No connection to GCN."); T::AddStateName(State::kConnected, "Connected", "Connection to GCN established."); T::AddStateName(State::kValid, "Valid", "Connection valid (keep alive received within past 2min)"); // Verbosity commands T::AddEvent("SET_VERBOSE", "B:1") (bind(&StateMachineGCN::SetVerbosity, this, placeholders::_1)) ("set verbosity state" "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data"); T::AddEvent("SET_DEBUG_RX", "B:1") (bind(&StateMachineGCN::SetDebugRx, this, placeholders::_1)) ("Set debux-rx state" "|debug[bool]:dump received text and parsed text to console (yes/no)"); // Conenction commands T::AddEvent("DISCONNECT", State::kConnected) (bind(&StateMachineGCN::Disconnect, this)) ("disconnect from ethernet"); T::AddEvent("RECONNECT", "O", State::kDisconnected, State::kConnected) (bind(&StateMachineGCN::Reconnect, this, placeholders::_1)) ("(Re)connect ethernet connection to FTM, a new address can be given" "|[host][string]:new ethernet address in the form "); fGCN.StartConnect(); } void SetEndpoint(const string &url) { vector v; v.push_back(url); fGCN.SetEndPoints(v); } int EvalOptions(Configuration &conf) { fGCN.SetVerbose(!conf.Get("quiet")); fGCN.SetEndPoints(conf.Vec("addr")); fGCN.SetURI(conf.Get("schedule-database")); return -1; } }; // ------------------------------------------------------------------------ #include "Main.h" template int RunShell(Configuration &conf) { return Main::execute>(conf); } void SetupConfiguration(Configuration &conf) { po::options_description control("FTM control options"); control.add_options() ("no-dim", po_bool(), "Disable dim services") ("addr,a", vars(), "Network addresses of GCN server") ("quiet,q", po_bool(true), "Disable printing contents of all received messages (except dynamic data) in clear text.") ("schedule-database", var(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].") ; conf.AddOptions(control); } /* Extract usage clause(s) [if any] for SYNOPSIS. Translators: "Usage" and "or" here are patterns (regular expressions) which are used to match the usage synopsis in program output. An example from cp (GNU coreutils) which contains both strings: Usage: cp [OPTION]... [-T] SOURCE DEST or: cp [OPTION]... SOURCE... DIRECTORY or: cp [OPTION]... -t DIRECTORY SOURCE... */ void PrintUsage() { cout << "The gcn reads and evaluates alerts from the GCN network.\n" "\n" "The default is that the program is started without user intercation. " "All actions are supposed to arrive as DimCommands. Using the -c " "option, a local shell can be initialized. With h or help a short " "help message about the usuage can be brought to the screen.\n" "\n" "Usage: gcn [-c type] [OPTIONS]\n" " or: gcn [OPTIONS]\n"; cout << endl; } void PrintHelp() { Main::PrintHelp>(); /* Additional help text which is printed after the configuration options goes here */ /* cout << "bla bla bla" << endl << endl; cout << endl; cout << "Environment:" << endl; cout << "environment" << endl; cout << endl; cout << "Examples:" << endl; cout << "test exam" << endl; cout << endl; cout << "Files:" << endl; cout << "files" << endl; cout << endl; */ } int main(int argc, const char* argv[]) { Configuration conf(argv[0]); conf.SetPrintUsage(PrintUsage); Main::SetupConfiguration(conf); SetupConfiguration(conf); if (!conf.DoParse(argc, argv, PrintHelp)) return 127; //try { // No console access at all if (!conf.Has("console")) { if (conf.Get("no-dim")) return RunShell(conf); else return RunShell(conf); } // Cosole access w/ and w/o Dim if (conf.Get("no-dim")) { if (conf.Get("console")==0) return RunShell(conf); else return RunShell(conf); } else { if (conf.Get("console")==0) return RunShell(conf); else return RunShell(conf); } } /*catch (std::exception& e) { cerr << "Exception: " << e.what() << endl; return -1; }*/ return 0; }