source: trunk/FACT++/src/scheduler.cc@ 19512

Last change on this file since 19512 was 19508, checked in by tbretz, 6 years ago
Instead of having a dry-run option, the scheduler can now be enabled/disabled from its command interface via START/STOP flagged by a new state 'Armed', added calculation of the relative threshold, separated calculation of source position and wobble position from calculation of schedule, only calculate the wobble position if there is a need, implemented a known_only flag which allows only for observation of already known sources, for convenience, list all gamma-ray sources within the field-of-view. Entries into the database are now enabled. The INTERRUPT send for preparation is now flagged as 'prepare' to allow Main.js to STOP the current pointing and wait for it before RELOAD_SOURCES is issued.
File size: 42.0 KB
Line 
1#include "Prediction.h"
2
3#include <boost/algorithm/string/join.hpp>
4
5#include "Database.h"
6
7#include "HeadersToO.h"
8#include "HeadersGCN.h"
9
10#include "EventImp.h"
11#include "LocalControl.h"
12#include "StateMachineDim.h"
13
14#include "Dim.h"
15#include "tools.h"
16#include "Time.h"
17#include "Configuration.h"
18
19using namespace std;
20using namespace Nova;
21
22// -----------------------------------------------------------------------
23
24/*
25void SetupConfiguration(Configuration &conf);
26{
27 po::options_description control("Makeschedule");
28 control.add_options()
29 ("uri", var<string>(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
30 ;
31
32 // po::positional_options_description p;
33 // p.add("date", 1); // The first positional options
34
35 conf.AddOptions(control);
36 //conf.SetArgumentPositions(p);
37}
38
39void PrintUsage();
40{
41 cout <<
42 "makeschedule - Creates an automatic schedule\n"
43 "\n"
44 "Usage: makeschedule [yyyy-mm-dd]\n";
45 cout << endl;
46}
47
48void PrintHelp();
49{
50 cout << endl;
51}*/
52
53struct CheckVisibility
54{
55 // Input
56 float moon_min = 10;
57 float moon_max = 170;
58 float sun_max = -12;
59 float zd_max = 75;
60 float current_max = 110;
61 float threshold_max = 10;
62
63 // Output
64 Nova::SolarObjects solarobj;
65 Nova::ZdAzPosn position;
66
67 double moon_dist = -1;
68 double current = -1;
69 double threshold = -1;
70
71 bool valid_zd = false;
72 bool valid_current = false;
73 bool valid_sun = false;
74 bool valid_moon = false;
75 bool valid_threshold = false;
76
77 bool visible = false;
78
79 void calc(const Nova::EquPosn &equ, const double &jd)
80 {
81 solarobj = Nova::SolarObjects(jd);
82 position = Nova::GetHrzFromEqu(equ, jd);
83 moon_dist = Nova::GetAngularSeparation(equ, solarobj.fMoonEqu);
84
85 current = FACT::PredictI(solarobj, equ);
86
87 const double ratio = pow(cos(position.zd*M_PI/180), -2.664);
88
89 threshold = position.zd<90 ? ratio*pow(current/6.2, 0.394) : -1;
90
91 valid_moon = moon_dist>moon_min && moon_dist<moon_max;
92 valid_zd = position.zd<zd_max;
93 valid_sun = solarobj.fSunHrz.alt<sun_max;
94 valid_current = current<current_max;
95 valid_threshold = threshold>0 && threshold<threshold_max;
96
97 visible = valid_moon && valid_zd && valid_sun && valid_current;
98 }
99
100 CheckVisibility(const Nova::EquPosn &equ, const double &jd)
101 {
102 calc(equ, jd);
103 }
104
105 CheckVisibility(const Nova::EquPosn &equ)
106 {
107 calc(equ, Time().JD());
108 }
109
110 CheckVisibility(const double &ra, const double &dec, const double &jd)
111 {
112 Nova::EquPosn equ;
113 equ.ra = ra;
114 equ.dec = dec;
115 calc(equ, jd);
116 }
117
118 CheckVisibility(const double &ra, const double &dec)
119 {
120 Nova::EquPosn equ;
121 equ.ra = ra;
122 equ.dec = dec;
123 calc(equ, Time().JD());
124 }
125};
126
127/*
128
129 * Visibility check
130 * Source eintragen
131 * RELOAD SOURCES
132 * Schedule eintragen
133 * Send 'RESCHEDULE' interrupt
134
135*/
136/*
137int mainx(int argc, const char* argv[])
138{
139 Configuration conf(argv[0]);
140 conf.SetPrintUsage(PrintUsage);
141 SetupConfiguration(conf);
142
143 if (!conf.DoParse(argc, argv, PrintHelp))
144 return 127;
145
146 // ------------------ Eval config ---------------------
147
148 const string uri = conf.Get<string>("schedule-database");
149
150 const uint16_t verbose = 1;
151 const bool dry_run = true;
152
153 Time stopwatch;
154
155 Database connection(uri); // Keep alive while fetching rows
156
157 cout << Time()-stopwatch << endl;
158
159 if (verbose>0)
160 cout << "Server Version: " << connection.server_version() << endl;
161
162 const int source_key = 999; // source_key of the source to be scheduled.
163
164 const uint16_t contingency = 0; // The contingency to delete earlier entries from the Table
165 const uint16_t duration = 300; // The target duration of the ToO
166 const uint16_t required = 20; // If the ToO would be less than required, skip_it
167 const uint16_t skip_time = 20; // If following observation would be less than skip_time, skip it
168
169 if (duration<required)
170 {
171 cerr << "Requested duration is smaller than required time." << endl;
172 return 0;
173 }
174
175 Time day(2019, 1, 24, 17, 0, 0);
176 Time now(2019, 1, 24, 19, 59, 0);
177 Time next = day+boost::posix_time::hours(14);
178 Time restart = now+boost::posix_time::minutes(duration);
179
180 // Safety margin
181 now -= boost::posix_time::seconds(contingency);
182
183 cout << '\n';
184 cout << "Contingency: " << contingency << " s\n";
185 cout << "Start of ToO: " << now.GetAsStr() << '\n';
186 cout << "End of ToO: " << restart.GetAsStr() << '\n';
187 cout << "Duration: " << duration << " min\n\n";
188
189 cout << "Schedule:\n";
190
191 const string queryS =
192 "SELECT *,"
193 " (fMeasurementTypeKey=4 AND fStart<='"+restart.GetAsStr()+"') AS `Reschedule`,"
194 " (fStart>'"+restart.GetAsStr()+"') AS `Following`,"
195 " (fMeasurementTypeKey=4 AND fStart BETWEEN '"+now.GetAsStr()+"' AND '"+restart.GetAsStr()+"') AS `Delete`"
196 " FROM Schedule"
197 " WHERE fStart BETWEEN '"+day.GetAsStr()+"' AND '"+next.GetAsStr()+"'"
198 " AND fMeasurementID=0"
199 " ORDER BY fStart ASC, fMeasurementID ASC";
200
201 const mysqlpp::StoreQueryResult resS =
202 connection.query(queryS).store();
203
204 vector<string> list;
205
206 int32_t firstdata = -1;
207 int32_t reschedule = -1;
208 int32_t following = -1;
209 for (size_t i=0; i<resS.num_rows(); i++)
210 {
211 const mysqlpp::Row &row = resS[i];
212
213 cout << setw(2) << i << " | ";
214 cout << row["Reschedule"] << " | ";
215 cout << row["Following"] << " | ";
216 cout << row["fScheduleID"] << " | ";
217 cout << row["fStart"] << " | ";
218 if (bool(row["Delete"]))
219 {
220 cout << " < delete > | ";
221 list.push_back((string)row["fScheduleID"]);
222 }
223 else
224 cout << row["fLastUpdate"] << " | ";
225 cout << row["fMeasurementID"] << " | ";
226 cout << row["fUser"] << " | ";
227 cout << row["fData"] << " | " << setw(4);
228 cout << row["fSourceKey"] << " | ";
229 cout << row["fMeasurementTypeKey"] << '\n';
230
231 if (bool(row["Reschedule"]))
232 reschedule = i;
233
234 if (following==-1 && bool(row["Following"]))
235 following = i;
236
237 uint16_t type = row["fMeasurementTypeKey"];
238 if (firstdata==-1 && type==4)
239 firstdata = i;
240 }
241
242 // --------------------------------------------------------------------
243
244 if (firstdata==-1)
245 {
246 cerr << "No data run found." << endl;
247 return 0;
248 }
249
250 // --------------------------------------------------------------------
251
252 if (reschedule>=0)
253 cout << "\nLast data run before restart at " << restart.GetAsStr() << ": idx=" << reschedule << " / ID=" << resS[reschedule]["fScheduleID"] << "\n";
254
255 // --------------------------------------------------------------------
256
257 const Time first((string)resS[firstdata]["fStart"]); // First data run of the night
258 const Time last( (string)resS[resS.num_rows()-1]["fStart"]); // Last scheduled observation of the night
259
260 // --------------------------------------------------------------------
261
262 if (restart<=first)
263 {
264 cout << "ToO ends before first data taking... skipped." << endl;
265 return 0;
266 }
267 if (now>=last)
268 {
269 cout << "ToO starts after data taking... skipped." << endl;
270 return 0;
271 }
272
273 // --------------------------------------------------------------------
274
275 if (now<first)
276 {
277 cout << "ToO starts before first data taking... rescheduling start time." << endl;
278 now = first;
279 }
280 if (restart>last)
281 {
282 cout << "ToO ends after data taking... rescheduling end time!" << endl;
283 restart = last;
284 }
285
286 // --------------------------------------------------------------------
287
288 if (restart<now+boost::posix_time::minutes(required))
289 {
290 cout << "Can not schedule more than " << required << "minutes... skipped." << endl;
291 return 0;
292 }
293
294 // --------------------------------------------------------------------
295
296 if (following>=0)
297 {
298 const Time follow((string)resS[following]["fStart"]);
299 if (follow<restart+boost::posix_time::minutes(skip_time))
300 {
301 cout << "Following observation would be less than " << skip_time << " min... skipping rescheduled source." << endl;
302 reschedule = -1;
303 }
304 }
305
306 // ====================================================================
307
308 if (!list.empty())
309 {
310 cout << "\nDelete entries:\n";
311 for (const auto &l : list)
312 cout << l << "\n";
313 cout << endl;
314 }
315
316 // ====================================================================
317
318 vector<string> insert;
319
320 cout << "New entries:\n";
321 cout << " | auto | " << now.GetAsStr() << " | < start > | 0 | ToO | NULL | | 4\n";
322
323 insert.emplace_back("('"+now.GetAsStr()+"',0,'ToO',NULL,"+to_string(source_key)+",4)");
324
325 // --------------------------------------------------------------------
326
327 if (reschedule>=0 && restart!=last)
328 {
329 cout << " | auto | " << restart.GetAsStr() << " | < end > | 0 | ToO | NULL | " << setw(4) << resS[reschedule]["fSourceKey"] << " | 4\n";
330
331 const string fData = (string)resS[reschedule]["fData"];
332 const string fKey = (string)resS[reschedule]["fSourceKey"];
333
334 const string data = resS[reschedule]["fData"] ? "'"+fData+"'" : "NULL";
335 insert.emplace_back("('"+restart.GetAsStr()+"',0,'ToO',"+data+","+fKey+",4)");
336 }
337
338 // ====================================================================
339
340 cout << Time()-stopwatch << endl;
341
342 // ====================================================================
343
344 if (insert.empty())
345 {
346 cout << "No new schedule." << endl;
347 return 0;
348 }
349
350 const string queryD = string("DELETE FROM Schedule WHERE fScheduleID IN (")+boost::algorithm::join(list, ",")+")";
351
352 const string queryI =
353 "INSERT (fStart, fMeasurementID, fUser, fData, fSoureKey, fMeasurementTypeKey) "
354 "VALUES\n "+string(boost::algorithm::join(insert, ",\n "));
355
356 if (dry_run)
357 {
358 cout << "\n";
359 if (!list.empty())
360 cout << queryD << "\n\n";
361 if (!insert.empty())
362 cout << queryI << "\n\n";
363 cout << flush;
364 return 0;
365 }
366
367 // Empty interrupt to stop data taking as early as possible
368 Dim::SendCommandNB("DIM_CONTROL/INTERRUPT");
369
370 // Reload sources as early as possible
371 Dim::SendCommandNB("DRIVE_CONTROL/RELOAD_SOURCES");
372
373 // Start pointing procedure as early as possible
374 //Dim::SendCommand("DRIVE_CONTROL/TRACK_WOBBLE", wobble, obs[sub].source);
375
376 try
377 {
378 // ---------------------------------------------------------
379
380 connection.query("LOCK TABLES Schedule WRITE");
381
382 // ---------------------------------------------------------
383
384 if (!list.empty())
385 {
386 const mysqlpp::SimpleResult res = connection.query(queryD).execute();
387 cout << res.rows() << " row(s) deleted.\n" << endl;
388 }
389
390 // ---------------------------------------------------------
391
392 if (!insert.empty())
393 {
394 auto q = connection.query(queryI);
395 q.execute();
396 cout << q.info() << '\n' << endl;
397 }
398
399 // ---------------------------------------------------------
400
401 connection.query("UNLOCK TABLES");
402
403 // ---------------------------------------------------------
404 }
405 catch (const exception &e)
406 {
407 cerr << "SQL query failed: " << e.what() << endl;
408 return 7;
409 }
410
411 Dim::SendCommand("DIM_CONTROL/INTERRUPT", "reschedule");
412
413 return 0;
414}
415*/
416// ------------------------------------------------------------------------
417
418class StateMachineToO : public StateMachineDim
419{
420private:
421 string fUri;
422 Database fConnection;
423 uint16_t fVerbose;
424
425 Time fKeepAliveDeadline;
426 uint16_t fKeepAliveInterval;
427
428 int16_t GetSourceKey(const string &name, const double &ra, const double &dec)
429 {
430 const double min_dist = 0.1;
431 const double check_dist = 2.0;
432
433
434 int32_t source_key = -1;
435
436 const string query1 =
437 "SELECT fSourceKey, fSourceName, ADIST(fDeclination, fRightAscension*15, "+to_string(dec)+", "+to_string(ra)+") AS Dist\n"
438 " FROM Source\n"
439 " WHERE fSourceTypeKEY=1\n"
440 " HAVING Dist<"+to_string(check_dist)+"\n"
441 " ORDER BY Dist ASC";
442
443 const mysqlpp::StoreQueryResult res1 = fConnection.query(query1).store();
444
445 if (res1.num_rows())
446 {
447 Info("Found "+to_string(res1.num_rows())+" sources with a distance less than "+to_string(check_dist)+"\u00b0");
448
449 for (size_t i=0; i<res1.num_rows(); i++)
450 {
451 const mysqlpp::Row &row = res1[i];
452 Info(" "+string(row["fSourceName"])+" ["+string(row["fSourceKey"])+"]: D="+string(row["Dist"])+"\u00b0");
453 }
454
455 const mysqlpp::Row &row = res1[0];
456 if (double(row["Dist"])<min_dist)
457 {
458 Warn("Sources closer than "+to_string(check_dist)+"\u00b0 detected.");
459 Warn("Overwriting source key with: "+string(row["fSourceName"])+" ["+string(row["fSourceKey"])+"]: D="+string(row["Dist"])+"\u00b0");
460 source_key = res1[0]["fSourceKey"];
461 }
462 }
463
464 //Out() << Time()-stopwatch << endl;
465
466 const string query2 =
467 "SELECT fSourceKey FROM Source WHERE fSourceName='"+name+"'";
468
469 const mysqlpp::StoreQueryResult res2 = fConnection.query(query2).store();
470
471 if (res2.num_rows())
472 {
473 source_key = res2[0]["fSourceKey"];
474 Info("A source with the same name (key="+to_string(source_key)+") was found in the Source table.");
475 }
476
477 return source_key;
478 }
479
480 float GetWobbleAngle(const double &ra, const double &dec)
481 {
482 const double wobble_offset = 0.6;
483 const double camera_radius = 2.3;
484 const double magnitude_max = 4.5;
485
486 /*
487 Mag Cnt
488 -2 1
489 -1 3
490 0 11
491 1 33
492 2 121
493 3 321
494 4 871
495 5 1364
496 6 404
497 7 12
498 */
499
500 // The wobble position lay in the plane which is normal the to the plane source-center-star
501 // and goes through
502
503 double wobble_angle = 0;
504
505 const string query0 =
506 "SELECT fSourceKEY, fRightAscension, fDeclination, fMagnitude,\n"
507 " ADIST(fDeclination, fRightAscension*15, "+to_string(dec)+", "+to_string(ra)+") AS Dist\n"
508 " FROM Source\n"
509 " WHERE (fSourceTypeKey=2 OR fSourceTypeKey=3) AND fMagnitude<"+to_string(magnitude_max)+"\n"
510 " HAVING Dist<"+to_string(camera_radius+wobble_offset)+"\n"
511 " ORDER BY fMagnitude ASC";
512
513 Out() << query0 << endl;
514
515 const mysqlpp::StoreQueryResult res0 = fConnection.query(query0).store();
516
517 Info("Found "+to_string(res0.num_rows())+" stars in the camera field-of-view with magnitude less than "+to_string(magnitude_max));
518
519 for (size_t i=0; i<::min<size_t>(10, res0.num_rows()); i++)
520 {
521 const mysqlpp::Row &row = res0[i];
522
523 // TVector3 souce, star;
524 // source.SetMagThetaPhi(1, rad(90-dec), rad(ra));
525 // star.SetMagThetaPhi( 1, rad(90-dec), rad(ra));
526 //
527 // star.RotateZ( -source.Phi());
528 // source.RotateZ(-source.Phi());
529 //
530 // star.RotateY( 180-source.Theta());
531 // source.RotateY(180-source.Theta());
532 //
533 // rho = star.Phi();
534
535 const double rad = M_PI/180;
536
537 const double dec0 = rad * dec;
538 const double ra0 = rad * ra;
539
540 const double dec1 = rad * row["fDeclination"];
541 const double ra1 = rad * row["fRightAscension"] * 15;
542
543 const double s2 = sin(ra0-ra1);
544 const double c2 = cos(ra0-ra1);
545
546 const double s0 = cos(dec0);
547 const double c0 = sin(dec0);
548
549 const double c1 = cos(dec1);
550 const double s1 = sin(dec1);
551
552 const double k0 = -s2*c1;
553 const double k1 = s0*s1 - c0*c2*c1;
554
555 const double rho = atan(k0/k1) / rad; // atan2(k0, k1)/rad
556
557 if (i==0)
558 wobble_angle = rho;
559
560 Info(" "+Tools::Form("Mag=%5.2f Dist=%5.1f Phi=%6.1f", (double)row["fMagnitude"], (double)row["Dist"], rho));
561 }
562
563 if (res0.num_rows())
564 {
565 Info("The brightest star (M="+string(res0[0]["fMagnitude"])+") at distance is visible at a wobble angle of "+Tools::Form("%.2f", wobble_angle)+"\u00b0");
566 Info("Wobble angles determined as "+Tools::Form("%.2f", wobble_angle-90)+"\u00b0 and "+Tools::Form("%.2f", wobble_angle+90)+"\u000b");
567 }
568 else
569 {
570 Info("Using default wobble angles.");
571 }
572
573 return wobble_angle;
574 }
575
576
577 bool ScheduleImp(const string &name, const double &ra, const double &dec, const bool &known_only)
578 {
579 const bool fDryRun = GetCurrentState()!=ToO::State::kArmed;
580
581 if (fDryRun)
582 Warn("Scheduler not armed!");
583
584 Time stopwatch;
585
586 // This is just a desperate try which will most likely fail
587 if (!fConnection.connected())
588 fConnection.reconnect();
589
590 /*
591 +-----------------+---------------------+------+-----+---------+----------------+
592 | Field | Type | Null | Key | Default | Extra |
593 +-----------------+---------------------+------+-----+---------+----------------+
594 | fSourceKEY | int(11) | NO | PRI | NULL | auto_increment |
595 | fSourceName | varchar(30) | NO | | NULL | |
596 | fRightAscension | double(10,6) | NO | | NULL | |
597 | fDeclination | double(10,6) | NO | | NULL | |
598 | fEpochKEY | tinyint(4) | YES | | NULL | |
599 | fFlux | float | YES | | NULL | |
600 | fSlope | float | YES | | NULL | |
601 | fSourceTypeKey | int(11) | NO | MUL | NULL | |
602 | fWobbleOffset | float | NO | | 0.6 | |
603 | fWobbleAngle0 | int(11) | NO | | 90 | |
604 | fWobbleAngle1 | int(11) | NO | | -90 | |
605 | fMagnitude | float(4,2) | YES | | NULL | |
606 | fIsToO | tinyint(3) unsigned | NO | | 0 | |
607 +-----------------+---------------------+------+-----+---------+----------------+
608 */
609
610 int32_t source_key = GetSourceKey(name, ra, dec);
611
612 Out() << Time()-stopwatch << endl;
613
614 if (known_only && source_key<0)
615 {
616 Warn("Observation of only known sources requested but no known source found.");
617 return false;
618 }
619
620 /*
621 +---------------------+--------------+------+-----+-------------------+-----------------------------+
622 | Field | Type | Null | Key | Default | Extra |
623 +---------------------+--------------+------+-----+-------------------+-----------------------------+
624 | fScheduleID | int(11) | NO | PRI | NULL | auto_increment |
625 | fStart | datetime | NO | MUL | NULL | |
626 | fLastUpdate | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
627 | fMeasurementID | int(11) | NO | | NULL | |
628 | fUser | varchar(32) | NO | | NULL | |
629 | fData | varchar(256) | YES | | NULL | |
630 | fSourceKey | smallint(6) | YES | | NULL | |
631 | fMeasurementTypeKey | tinyint(4) | NO | | NULL | |
632 +---------------------+--------------+------+-----+-------------------+-----------------------------+
633 */
634
635 const uint16_t contingency = 1; // The contingency to delete earlier entries from the Table
636 const uint16_t duration = 60; // The target duration of the ToO
637 const uint16_t required = 20; // If the ToO would be less than required, skip_it
638 const uint16_t skip_time = 20; // If following observation would be less than skip_time, skip it
639
640 if (duration<required)
641 {
642 Error("Requested duration is smaller than required time.");
643 return false;
644 }
645
646 const Time now;
647
648 const Time sunset = now.GetPrevSunSet();
649 const Time sunrise = now.GetNextSunRise();
650
651 if (sunset>sunrise || sunrise>sunset+boost::posix_time::hours(24))
652 {
653 Error("Unable to schedule a ToO during daytime.");
654 return false;
655 }
656
657 // Safety margin
658 Time schedtime = now-boost::posix_time::seconds(contingency);
659 Time restart = now+boost::posix_time::minutes(duration);
660
661 Out() << '\n';
662 Out() << "Contingency: " << contingency << " s\n";
663 Out() << "Start of ToO: " << now.GetAsStr() << '\n';
664 Out() << "End of ToO: " << restart.GetAsStr() << '\n';
665 Out() << "Duration: " << duration << " min\n\n";
666
667 Out() << "Schedule:\n";
668
669 const string queryS =
670 "SELECT *,\n"
671 " (fMeasurementTypeKey=4 AND fStart<='"+restart.GetAsStr()+"') AS `Reschedule`,\n"
672 " (fStart>'"+restart.GetAsStr()+"') AS `Following`,\n"
673 " (fMeasurementTypeKey=4 AND fStart BETWEEN '"+schedtime.GetAsStr()+"' AND '"+restart.GetAsStr()+"') AS `Delete`\n"
674 " FROM Schedule\n"
675 " WHERE fStart BETWEEN '"+sunset.GetAsStr()+"' AND '"+sunrise.GetAsStr()+"'\n"
676 " AND fMeasurementID=0\n"
677 " ORDER BY fStart ASC, fMeasurementID ASC";
678
679 const mysqlpp::StoreQueryResult resS = fConnection.query(queryS).store();
680
681 if (resS.num_rows()<2)
682 {
683 Error("Available schedule is too short to be evaluated.");
684 return false;
685 }
686
687 vector<string> list;
688
689 int32_t firstdata = -1;
690 int32_t reschedule = -1;
691 int32_t following = -1;
692 for (size_t i=0; i<resS.num_rows(); i++)
693 {
694 const mysqlpp::Row &row = resS[i];
695
696 Out() << setw(2) << i << " | ";
697 Out() << row["Reschedule"] << " | ";
698 Out() << row["Following"] << " | ";
699 Out() << row["fScheduleID"] << " | ";
700 Out() << row["fStart"] << " | ";
701 if (bool(row["Delete"]))
702 {
703 Out() << " < delete > | ";
704 list.push_back((string)row["fScheduleID"]);
705 }
706 else
707 Out() << row["fLastUpdate"] << " | ";
708 Out() << row["fMeasurementID"] << " | ";
709 Out() << row["fUser"] << " | ";
710 Out() << row["fData"] << " | " << setw(4);
711 Out() << row["fSourceKey"] << " | ";
712 Out() << row["fMeasurementTypeKey"] << '\n';
713
714 if (bool(row["Reschedule"]))
715 reschedule = i;
716
717 if (following==-1 && bool(row["Following"]))
718 following = i;
719
720 const uint16_t type = row["fMeasurementTypeKey"];
721 if (firstdata==-1 && type==4)
722 firstdata = i;
723 }
724
725 // --------------------------------------------------------------------
726
727 if (firstdata==-1)
728 {
729 Warn("No data run found.");
730 return false;
731 }
732
733 // --------------------------------------------------------------------
734
735 if (reschedule>=0)
736 Out() << "\nLast data run before restart at " << restart.GetAsStr() << ": idx=" << reschedule << " / ID=" << resS[reschedule]["fScheduleID"] << endl;
737
738 // --------------------------------------------------------------------
739
740 const Time first((string)resS[firstdata]["fStart"]); // First data run of the night
741 const Time last( (string)resS[resS.num_rows()-1]["fStart"]); // Last scheduled observation of the night
742
743 // --------------------------------------------------------------------
744
745 if (restart<=first)
746 {
747 Warn("ToO ends before first data taking... skipped.");
748 return false;
749 }
750 if (schedtime>=last)
751 {
752 Warn("ToO starts after data taking... skipped.");
753 return false;
754 }
755
756 // --------------------------------------------------------------------
757
758 if (schedtime<first)
759 {
760 Info("ToO starts before first data taking... rescheduling start time.");
761 schedtime = first;
762 }
763 if (restart>last)
764 {
765 Info("ToO ends after data taking... rescheduling end time!");
766 restart = last;
767 }
768
769 // --------------------------------------------------------------------
770
771 if (restart<schedtime+boost::posix_time::minutes(required))
772 {
773 Warn("Could not schedule more than the required "+to_string(required)+" minutes... skipped.");
774 return false;
775 }
776
777 // --------------------------------------------------------------------
778
779 if (following>=0)
780 {
781 const Time follow((string)resS[following]["fStart"]);
782 if (follow<restart+boost::posix_time::minutes(skip_time))
783 {
784 Info("Following observation would be less than "+to_string(skip_time)+" min... skipping rescheduled source.");
785 reschedule = -1;
786 }
787 }
788
789 // ====================================================================
790
791 if (!list.empty())
792 {
793 Out() << "\nDelete entries:\n";
794 for (const auto &l : list)
795 Out() << l << "\n";
796 Out() << endl;
797 }
798
799 // ====================================================================
800
801 // FIXME: Check if source is not already scheduled anyways
802
803 Info("Source will be scheduled.");
804
805 if (source_key<0)
806 {
807 const double wobble_angle = GetWobbleAngle(ra, dec);
808
809 Out() << Time()-stopwatch << endl;
810
811 const string query =
812 "INSERT INTO Source\n"
813 " (fSourceName, fRightAscension, fDeclination, fWobbleAngle0, fWobbleAngle1, fSourceTypeKey, fIsToO) VALUES\n"
814 " ('"+name+"', "+to_string(ra/15.)+", "+to_string(dec)+", "+to_string(wobble_angle-90)+", "+to_string(wobble_angle+90)+", 1, 1)";
815
816 if (!fDryRun)
817 {
818 auto q = fConnection.query(query);
819 q.execute();
820 Info(q.info());
821
822 source_key = q.insert_id();
823
824 Info(string(fDryRun?"[dry-run] ":"")+"The new source got the key "+to_string(source_key));
825 }
826 else
827 Out() << query << endl;
828
829 }
830
831 Out() << Time()-stopwatch << endl;
832
833 // ====================================================================
834
835 vector<string> insert;
836
837 Out() << "New entries:\n";
838 Out() << " | auto | " << schedtime.GetAsStr() << " | < start > | 0 | ToO | nodrs,grb | " << setw(4) << source_key << " | 4\n";
839
840 insert.emplace_back("('"+schedtime.GetAsStr()+"',0,'ToO','nodrs:true,grb:true',"+to_string(source_key)+",4)");
841
842 // --------------------------------------------------------------------
843
844 if (reschedule>=0 && restart!=last)
845 {
846 Out() << " | auto | " << restart.GetAsStr() << " | < end > | 0 | ToO | NULL | " << setw(4) << resS[reschedule]["fSourceKey"] << " | 4\n";
847
848 const string fData = (string)resS[reschedule]["fData"];
849 const string fKey = (string)resS[reschedule]["fSourceKey"];
850
851 const string data = resS[reschedule]["fData"] ? "'"+fData+"'" : "NULL";
852 insert.emplace_back("('"+restart.GetAsStr()+"',0,'ToO',"+data+","+fKey+",4)");
853 }
854
855 // ====================================================================
856
857 Out() << Time()-stopwatch << endl;
858
859 // ====================================================================
860
861 const string queryD = string("DELETE FROM Schedule WHERE fScheduleID IN (")+boost::algorithm::join(list, ",")+")";
862
863 const string queryI =
864 "INSERT INTO Schedule\n (fStart,fMeasurementID,fUser,fData,fSourceKey,fMeasurementTypeKey)\n"
865 "VALUES\n "+string(boost::algorithm::join(insert, ",\n "));
866
867 if (fDryRun)
868 {
869 Out() << "\n";
870 if (!list.empty())
871 Out() << queryD << "\n\n";
872 if (!insert.empty())
873 Out() << queryI << "\n\n";
874 Out() << flush;
875 return false;
876 }
877
878 // Empty interrupt to stop data taking as early as possible
879 // Triggers also RELOAD_SOURCES
880 Dim::SendCommandNB("DIM_CONTROL/INTERRUPT", "prepare");
881
882 // ---------------------------------------------------------
883
884 fConnection.query("LOCK TABLES Schedule WRITE");
885
886 // ---------------------------------------------------------
887 if (!list.empty())
888 {
889 const mysqlpp::SimpleResult res = fConnection.query(queryD).execute();
890 Info(to_string(res.rows())+" row(s) deleted from Schedule.");
891 }
892
893 // ---------------------------------------------------------
894
895 if (!insert.empty())
896 {
897 auto q = fConnection.query(queryI);
898 q.execute();
899 Info(q.info());
900 }
901 // ---------------------------------------------------------
902
903 fConnection.query("UNLOCK TABLES");
904
905 // ---------------------------------------------------------
906
907 Dim::SendCommand("DIM_CONTROL/INTERRUPT", "reschedule");
908
909 ostringstream out;
910 out << Time()-stopwatch;
911 Info(out);
912
913 return true;
914 }
915
916 bool Schedule(const string &name, const double &ra, const double &dec, const bool &known_only)
917 {
918 try
919 {
920 return ScheduleImp(name, ra, dec, known_only);
921 }
922 catch (const exception &e)
923 {
924 Error(string("SQL query failed: ")+e.what());
925 fConnection.disconnect();
926 return false;
927 }
928 }
929
930 // ========================================================================
931
932
933 // ========================================================================
934
935 bool CheckEventSize(size_t has, const char *name, size_t size)
936 {
937 if (has==size)
938 return true;
939
940 ostringstream msg;
941 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
942 Fatal(msg);
943 return false;
944 }
945
946 int ScheduleGCN(const EventImp &evt)
947 {
948 if (!CheckEventSize(evt.GetSize(), "ScheduleGCN", 2+4+3*8))
949 return kSM_FatalError;
950
951 const ToO::DataGRB &grb = evt.Ref<ToO::DataGRB>();
952
953 const auto it = GCN::PaketTypes.find(grb.type);
954
955 if (it==GCN::PaketTypes.end())
956 {
957 Warn("Unknown Packet Type: "+to_string(grb.type));
958 return GetCurrentState();
959 }
960
961 const auto &paket = it->second;
962
963 ostringstream out;
964 out << "Received: '" << paket.name << "' ID=" << grb.type << " NUM=" << grb.trigid << " RA=" << grb.ra/15. << "h DEC=" << grb.dec << "\u00b0 ERR=" << grb.err;
965
966 /*
967 const std::vector<int16_t> typelist =
968 {
969 51, // INTEGRAL_POINTDIR
970
971 53, // INTEGRAL_WAKEUP
972 54, // INTEGRAL_REFINED
973 55, // INTEGRAL_OFFLINE
974
975 // 56, // INTEGRAL_WEAK
976 // 59, // KONUS_LC
977
978 60, // SWIFT_BAT_GRB_ALERT
979 61, // SWIFT_BAT_GRB_POS_ACK
980 62, // SWIFT_BAT_GRB_POS_NACK
981
982 83, // SWIFT_POINTDIR
983
984 97, // SWIFT_BAT_QL_POS
985
986 100, // AGILE_GRB_WAKEUP
987 101, // AGILE_GRB_GROUND
988 102, // AGILE_GRB_REFINED
989
990 110, // FERMI_GBM_FLT_POS
991 111, // FERMI_GBM_GND_POS
992 112, // FERMI_GBM_LC
993 115, // FERMI_GBM_TRANS
994
995 123, // FERMI_LAT_TRANS
996 125, // FERMI_LAT_MONITOR
997
998 // 134, // MAXI_UNKNOWN
999 // 135, // MAXI_KNOWN
1000 // 136, // MAXI_TEST
1001
1002 157, // AMON_ICECUBE_COINC
1003 158, // AMON_ICECUBE_HESE
1004
1005 169, // AMON_ICECUBE_EHE
1006 171, // HAWC_BURST_MONITOR
1007 173, // ICECUBE_GOLD
1008 174, // ICECUBE_BRONZE
1009 };
1010 */
1011
1012 Info(out);
1013 Info(paket.description);
1014
1015 const CheckVisibility check(grb.ra, grb.dec);
1016
1017 //Info(string("Source is")+(check.visible?" ":" NOT ")+"visible.");
1018
1019 Info(string("Sun altitude: ")+(check.valid_sun ?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.solarobj.fSunHrz.alt)+"\u00b0]");
1020 Info(string("Moon distance: ")+(check.valid_moon ?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.moon_dist)+"\u00b0]");
1021 Info(string("Zenith angle: ")+(check.valid_zd ?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.position.zd)+"\u00b0]");
1022 Info(string("Current: ")+(check.valid_current ?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.current)+" \u00b5A]");
1023 Info(string("Rel. threshold: ")+(check.valid_threshold?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.threshold)+"]");
1024
1025 if (!check.visible)
1026 {
1027 Info("Source not observable... skipped.");
1028 return fConnection.connected() ? GetCurrentState() : ToO::State::kDisconnected;
1029 }
1030
1031 const string name = paket.instrument+"#"+to_string(grb.trigid);
1032 Info("Source is visible... name set to '"+name+"'");
1033
1034 /*
1035 * Swift: https://gcn.gsfc.nasa.gov/swift.html
1036 relevante Paket-Typen:
1037 60 BAT_Alert
1038 97 BAT_QL_Pos
1039 61 BAT_Pos
1040 62 BAT_Pos_Nack
1041 Es kann wohl vorkommen, dass ein Alert danach anders klassifiziert ist
1042 -> BAT_Trans (84) und es steht dass dann in der GRB notice eine catalog
1043 ID im comment steht. Da ist es mir noch nicht so ganz klar, woran man
1044 echte Alerts erkennt.
1045
1046 * Fermi: https://gcn.gsfc.nasa.gov/fermi.html
1047 relevante Paket-Typen:
1048 110 GBM_Alert P B B B&A
1049 111 GBM_Flt_Pos P B B B&A
1050 112 GBM_Gnd_Pos P B B B&A
1051 115 GBM_Final_Pos P B B B&A
1052 Bei GBM müssen wir usn überlegen wie wir es mit den Positionsupdates
1053 machen - das können auch mal ein paar Grad sein. siehe zB
1054 https://gcn.gsfc.nasa.gov/other/568833894.fermi
1055
1056 Wenn ich das richtig sehe, kommt bei GBM_Alert noch keine
1057 Position, sndern erst mit GBM_Flt_Pos (in dem Bsp kommt das 3 Sek
1058 später)
1059
1060 * INTEGRAL: https://gcn.gsfc.nasa.gov/integral.html
1061 wenn ich es richtig verstehe, sind die interessanten Alerts die WAKEUP
1062 (tw kommt ein Positionsupdate via OFFLINE oder REFINED). Es gibt auch
1063 noch WEAK, aber das sind scheinbar subthreshold alerts - würde ich
1064 jetzt erst mal weglassen. Im letzten Jahr gab es 8 WAKEUP alerts. 2017
1065 sogar nur 3, 2016 7, 2015 waren es einige mehr, aber das war V404_Cyg -
1066 in dem Fall steht dann aber ein Object-Name dabei - ansonsten steht as
1067 "Possbibly real GRB event" - zT steht auch, dass es coincident mit GBM
1068 events ist
1069
1070 * KONUS: https://gcn.gsfc.nasa.gov/konus.html
1071 Ist mir noch etwas unklar, was das genau ist - die schicken Lichtkurven
1072 von GRBs und Transients - muss man nochmal genauer schauen.
1073
1074 * MAXI: https://gcn.gsfc.nasa.gov/maxi.html
1075 wenn ich das richtig verstehe, sind das nicht nur GRBs -> müsste man
1076 nochmal genauer schauen bevor man da was implementiert
1077
1078 * AGILE: https://gcn.gsfc.nasa.gov/agile.html
1079 Wahrscheinlich eher nicht relevant, da steht was von 30min nach dem
1080 Burst kommt das 'Wakeup'. Die relevanten Paket-Typen wären 100
1081 (Wakeup_Pos), 101 (Ground_Pos) und 102 (Refined_Pos)
1082
1083 * AMON: https://gcn.gsfc.nasa.gov/amon.html
1084 sind bisher nur neutrinos - da macht MAGIC glaub ich schon automatic
1085 Follow-Up - müssen wir also kucken was Sinn macht
1086
1087 * Ligo-Virgo GW: https://gcn.gsfc.nasa.gov/lvc.html
1088 da ist natürlich die Error-Region groß - müsste man schauen, was man
1089 da will
1090
1091 Fazit: am relevantesten sind Fermi/GBM und Swift/BAT. Evtl könnte
1092 INTEGRAL noch Sinn machen und über AMON und LVC sollte man nachdenken,
1093 ob/was man haben will.
1094*/
1095
1096 const bool known_only = grb.type==51 || grb.type==83;
1097
1098 Schedule(name, grb.ra, grb.dec, known_only);
1099
1100 return fConnection.connected() ? GetCurrentState() : ToO::State::kDisconnected;
1101 //return GetCurrentState();
1102 }
1103
1104 int Enable()
1105 {
1106 return GetCurrentState()==ToO::State::kConnected ? ToO::State::kArmed : GetCurrentState();
1107 }
1108 int Disable()
1109 {
1110 return GetCurrentState()==ToO::State::kArmed ? ToO::State::kConnected : GetCurrentState();
1111 }
1112
1113 int Execute()
1114 {
1115 Time now;
1116 if (now>fKeepAliveDeadline)
1117 {
1118 static int ernum = 0;
1119 try
1120 {
1121 // Unfortunately, reconnecting [Takes about 1s] is
1122 // the only non-blocking way to ensure an open
1123 // connection. Ping would be nice but is blocking.
1124 fConnection = Database(fUri);
1125 ernum = 0;
1126 }
1127 catch (const mysqlpp::ConnectionFailed &e)
1128 {
1129 if (ernum!=e.errnum())
1130 Error(e.what());
1131 ernum = e.errnum();
1132 }
1133
1134 fKeepAliveDeadline += boost::posix_time::seconds(fKeepAliveInterval);
1135 }
1136
1137 if (!fConnection.connected())
1138 return ToO::State::kDisconnected;
1139
1140 if (fConnection.connected() && GetCurrentState()<=ToO::State::kDisconnected)
1141 return ToO::State::kConnected;
1142
1143 return GetCurrentState();
1144 }
1145
1146public:
1147 StateMachineToO(ostream &out=cout) : StateMachineDim(out, "SCHEDULER"),
1148 fConnection(""), fKeepAliveDeadline(Time())
1149 {
1150 AddStateName(ToO::State::kDisconnected, "Disconnected",
1151 "The Dim DNS is reachable, but the required subsystems are not available.");
1152
1153 AddStateName(ToO::State::kConnected, "Connected",
1154 "All needed subsystems are connected to their hardware, no action is performed.");
1155
1156 AddStateName(ToO::State::kArmed, "Armed",
1157 "All needed subsystems are connected to their hardware, scheduling in progress.");
1158
1159 AddEvent("GCN", "S:1;I:1;D:1;D:1;D:1")
1160 (bind(&StateMachineToO::ScheduleGCN, this, placeholders::_1))
1161 (""
1162 "|type[int16]:TypeID (see HeaderGCN.h)"
1163 "|trig[uint32]:TriggerID"
1164 "|ra[deg]:Right ascension"
1165 "|dec[deg]:Declination"
1166 "|err[deg]:Error radius");
1167
1168 AddEvent("START", "")
1169 (bind(&StateMachineToO::Enable, this/*, placeholders::_1*/))
1170 ("");
1171
1172 AddEvent("STOP", "")
1173 (bind(&StateMachineToO::Disable, this/*, placeholders::_1*/))
1174 ("");
1175 }
1176
1177 /*
1178 bool GetConfig(Configuration &conf, const string &name, const string &sub, uint16_t &rc)
1179 {
1180 if (conf.HasDef(name, sub))
1181 {
1182 rc = conf.GetDef<uint16_t>(name, sub);
1183 return true;
1184 }
1185
1186 Error("Neither "+name+"default nor "+name+sub+" found.");
1187 return false;
1188 }*/
1189
1190 int EvalOptions(Configuration &conf)
1191 {
1192 fVerbose = !conf.Get<bool>("quiet");
1193 fKeepAliveInterval = conf.Get<uint16_t>("keep-alive");
1194 fUri = conf.Get<string>("schedule-database");
1195 return -1;
1196 }
1197};
1198
1199
1200// ------------------------------------------------------------------------
1201
1202#include "Main.h"
1203
1204template<class T>
1205int RunShell(Configuration &conf)
1206{
1207 return Main::execute<T, StateMachineToO>(conf);
1208}
1209
1210void SetupConfiguration(Configuration &conf)
1211{
1212 po::options_description control("Scheduler");
1213 control.add_options()
1214 ("quiet,q", po_bool(), "")
1215 ("keep-alive", var<uint16_t>(uint16_t(300)), "Interval in seconds to ping (reconnect) the database server")
1216 ("schedule-database", var<string>(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
1217 ;
1218
1219 conf.AddOptions(control);
1220}
1221
1222void PrintUsage()
1223{
1224 cout <<
1225 "The scheduler program is able to schedule a new observation.\n"
1226 "\n"
1227 "Usage: scheduler [-c type] [OPTIONS]\n"
1228 " or: scheduler [OPTIONS]\n";
1229 cout << endl;
1230}
1231
1232void PrintHelp()
1233{
1234 Main::PrintHelp<StateMachineToO>();
1235}
1236
1237int main(int argc, const char* argv[])
1238{
1239 Configuration conf(argv[0]);
1240 conf.SetPrintUsage(PrintUsage);
1241 Main::SetupConfiguration(conf);
1242 SetupConfiguration(conf);
1243
1244 if (!conf.DoParse(argc, argv, PrintHelp))
1245 return 127;
1246
1247 if (!conf.Has("console"))
1248 return RunShell<LocalStream>(conf);
1249
1250 if (conf.Get<int>("console")==0)
1251 return RunShell<LocalShell>(conf);
1252 else
1253 return RunShell<LocalConsole>(conf);
1254
1255 return 0;
1256}
1257
Note: See TracBrowser for help on using the repository browser.