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

Last change on this file since 19516 was 19516, checked in by tbretz, 5 years ago
Do not trigger reschedule if source is already observed, added TH dependent checks for known sources, take ToO flag into account, some improvements to the output
File size: 43.9 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 struct Source
429 {
430 int16_t key { -1 };
431 bool isToO { false };
432 };
433
434 Source GetSourceKey(const string &name, const double &ra, const double &dec)
435 {
436 const double min_dist = 0.1;
437 const double check_dist = 2.0;
438
439 // ----------------------- Check if source with same name exists -----------------------
440
441 const string query2 =
442 "SELECT fSourceKey, fIsToO FROM Source WHERE fSourceName='"+name+"'";
443
444 const mysqlpp::StoreQueryResult res2 = fConnection.query(query2).store();
445
446 if (res2.num_rows())
447 {
448 Source source;
449 source.key = res2[0]["fSourceKey"];
450 source.isToO = res2[0]["fIsToO"];
451
452 Info("A source with the same name (key="+to_string(source.key)+") was found in the Source table.");
453
454 return source;
455 }
456
457 // ----------------- Check if source with similar coordinates exists -------------------
458
459 const string query1 =
460 "SELECT\n"
461 " fSourceKey,\n"
462 " fSourceName,\n"
463 " ADIST(fDeclination, fRightAscension*15, "+to_string(dec)+", "+to_string(ra)+") AS Dist,\n"
464 " fIsToO\n"
465 " FROM Source\n"
466 " WHERE fSourceTypeKEY=1\n"
467 " HAVING Dist<"+to_string(check_dist)+"\n"
468 " ORDER BY Dist ASC";
469
470 const mysqlpp::StoreQueryResult res1 = fConnection.query(query1).store();
471
472 if (res1.num_rows())
473 {
474 Info("Found "+to_string(res1.num_rows())+" sources with a distance less than "+Tools::Form("%.2f", check_dist)+"\u00b0");
475
476 for (size_t i=0; i<res1.num_rows(); i++)
477 {
478 const mysqlpp::Row &row = res1[i];
479 Info(" "+string(row["fSourceName"])+" ["+string(row["fSourceKey"])+"]: D="+Tools::Form("%.2f", double(row["Dist"]))+"\u00b0");
480 }
481
482 const mysqlpp::Row &row = res1[0];
483 if (double(row["Dist"])<min_dist)
484 {
485 Source source;
486 source.key = res1[0]["fSourceKey"];
487 source.isToO = res1[0]["fIsToO"];
488
489 Warn("Sources closer than "+Tools::Form("%.2f", check_dist)+"\u00b0 detected.");
490 Warn("Overwriting source key with: "+string(row["fSourceName"])+" ["+to_string(source.key)+"]: D="+Tools::Form("%.2f", double(row["Dist"]))+"\u00b0");
491
492 return source;
493 }
494 }
495
496 return Source();
497 }
498
499 float GetWobbleAngle(const double &ra, const double &dec)
500 {
501 const double wobble_offset = 0.6;
502 const double camera_radius = 2.3;
503 const double magnitude_max = 4.5;
504
505 /*
506 Mag Cnt
507 -2 1
508 -1 3
509 0 11
510 1 33
511 2 121
512 3 321
513 4 871
514 5 1364
515 6 404
516 7 12
517 */
518
519 // The wobble position lay in the plane which is normal the to the plane source-center-star
520 // and goes through
521
522 double wobble_angle = 0;
523
524 const string query0 =
525 "SELECT fSourceKEY, fRightAscension, fDeclination, fMagnitude,\n"
526 " ADIST(fDeclination, fRightAscension*15, "+to_string(dec)+", "+to_string(ra)+") AS Dist\n"
527 " FROM Source\n"
528 " WHERE (fSourceTypeKey=2 OR fSourceTypeKey=3) AND fMagnitude<"+to_string(magnitude_max)+"\n"
529 " HAVING Dist<"+to_string(camera_radius+wobble_offset)+"\n"
530 " ORDER BY fMagnitude ASC";
531
532 Out() << query0 << endl;
533
534 const mysqlpp::StoreQueryResult res0 = fConnection.query(query0).store();
535
536 Info("Found "+to_string(res0.num_rows())+" stars in the camera field-of-view with magnitude less than "+to_string(magnitude_max));
537
538 for (size_t i=0; i<::min<size_t>(10, res0.num_rows()); i++)
539 {
540 const mysqlpp::Row &row = res0[i];
541
542 // TVector3 souce, star;
543 // source.SetMagThetaPhi(1, rad(90-dec), rad(ra));
544 // star.SetMagThetaPhi( 1, rad(90-dec), rad(ra));
545 //
546 // star.RotateZ( -source.Phi());
547 // source.RotateZ(-source.Phi());
548 //
549 // star.RotateY( 180-source.Theta());
550 // source.RotateY(180-source.Theta());
551 //
552 // rho = star.Phi();
553
554 const double rad = M_PI/180;
555
556 const double dec0 = rad * dec;
557 const double ra0 = rad * ra;
558
559 const double dec1 = rad * row["fDeclination"];
560 const double ra1 = rad * row["fRightAscension"] * 15;
561
562 const double s2 = sin(ra0-ra1);
563 const double c2 = cos(ra0-ra1);
564
565 const double s0 = cos(dec0);
566 const double c0 = sin(dec0);
567
568 const double c1 = cos(dec1);
569 const double s1 = sin(dec1);
570
571 const double k0 = -s2*c1;
572 const double k1 = s0*s1 - c0*c2*c1;
573
574 const double rho = atan(k0/k1) / rad; // atan2(k0, k1)/rad
575
576 if (i==0)
577 wobble_angle = rho;
578
579 Info(" "+Tools::Form("Mag=%5.2f Dist=%5.1f Phi=%6.1f", (double)row["fMagnitude"], (double)row["Dist"], rho));
580 }
581
582 if (res0.num_rows())
583 {
584 Info("The brightest star (M="+string(res0[0]["fMagnitude"])+") at distance is visible at a wobble angle of "+Tools::Form("%.2f", wobble_angle)+"\u00b0");
585 Info("Wobble angles determined as "+Tools::Form("%.2f", wobble_angle-90)+"\u00b0 and "+Tools::Form("%.2f", wobble_angle+90)+"\u000b");
586 }
587 else
588 {
589 Info("Using default wobble angles.");
590 }
591
592 return wobble_angle;
593 }
594
595
596 bool ScheduleImp(const string &name, const double &ra, const double &dec, const CheckVisibility &check, const bool &is_pointing)
597 {
598 const bool fDryRun = GetCurrentState()!=ToO::State::kArmed;
599
600 if (fDryRun)
601 Warn("Scheduler not armed!");
602
603 Time stopwatch;
604
605 // This is just a desperate try which will most likely fail
606 if (!fConnection.connected())
607 fConnection.reconnect();
608
609 /*
610 +-----------------+---------------------+------+-----+---------+----------------+
611 | Field | Type | Null | Key | Default | Extra |
612 +-----------------+---------------------+------+-----+---------+----------------+
613 | fSourceKEY | int(11) | NO | PRI | NULL | auto_increment |
614 | fSourceName | varchar(30) | NO | | NULL | |
615 | fRightAscension | double(10,6) | NO | | NULL | |
616 | fDeclination | double(10,6) | NO | | NULL | |
617 | fEpochKEY | tinyint(4) | YES | | NULL | |
618 | fFlux | float | YES | | NULL | |
619 | fSlope | float | YES | | NULL | |
620 | fSourceTypeKey | int(11) | NO | MUL | NULL | |
621 | fWobbleOffset | float | NO | | 0.6 | |
622 | fWobbleAngle0 | int(11) | NO | | 90 | |
623 | fWobbleAngle1 | int(11) | NO | | -90 | |
624 | fMagnitude | float(4,2) | YES | | NULL | |
625 | fIsToO | tinyint(3) unsigned | NO | | 0 | |
626 +-----------------+---------------------+------+-----+---------+----------------+
627 */
628
629 Source source = GetSourceKey(name, ra, dec);
630
631 Out() << Time()-stopwatch << endl;
632
633 if (is_pointing && source.key<0) // Not a known source
634 {
635 Warn("Observation of only known sources requested but no known source found.");
636 return false;
637 }
638
639 if (source.key>=0)
640 {
641 if (source.isToO)
642 {
643 Warn("Known source with ToO flag... skipping.");
644 return false;
645 }
646
647 const set<uint16_t> prioritized_sources = { 1, 2, 7 };
648
649 const bool prio = prioritized_sources.find(source.key)!=prioritized_sources.end();
650
651 if ((check.threshold>3 && !prio) || check.threshold>10)
652 {
653 Warn("Relative threshold too high... skipping.");
654 return false;
655 }
656 }
657
658 /*
659 +---------------------+--------------+------+-----+-------------------+-----------------------------+
660 | Field | Type | Null | Key | Default | Extra |
661 +---------------------+--------------+------+-----+-------------------+-----------------------------+
662 | fScheduleID | int(11) | NO | PRI | NULL | auto_increment |
663 | fStart | datetime | NO | MUL | NULL | |
664 | fLastUpdate | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
665 | fMeasurementID | int(11) | NO | | NULL | |
666 | fUser | varchar(32) | NO | | NULL | |
667 | fData | varchar(256) | YES | | NULL | |
668 | fSourceKey | smallint(6) | YES | | NULL | |
669 | fMeasurementTypeKey | tinyint(4) | NO | | NULL | |
670 +---------------------+--------------+------+-----+-------------------+-----------------------------+
671 */
672
673 const uint16_t contingency = 1; // The contingency to delete earlier entries from the Table
674 const uint16_t duration = 60; // The target duration of the ToO
675 const uint16_t required = 20; // If the ToO would be less than required, skip_it
676 const uint16_t skip_time = 20; // If following observation would be less than skip_time, skip it
677
678 if (duration<required)
679 {
680 Error("Requested duration is smaller than required time.");
681 return false;
682 }
683
684 const Time now;
685
686 const Time sunset = now.GetPrevSunSet();
687 const Time sunrise = now.GetNextSunRise();
688
689 if (sunset>sunrise || sunrise>sunset+boost::posix_time::hours(24))
690 {
691 Error("Unable to schedule a ToO during daytime.");
692 return false;
693 }
694
695 // Safety margin
696 Time schedtime = now-boost::posix_time::seconds(contingency);
697 Time restart = now+boost::posix_time::minutes(duration);
698
699 Out() << '\n';
700 Out() << "Contingency: " << contingency << " s\n";
701 Out() << "Start of ToO: " << now.GetAsStr() << '\n';
702 Out() << "End of ToO: " << restart.GetAsStr() << '\n';
703 Out() << "Duration: " << duration << " min\n\n";
704
705 Out() << "Schedule:\n";
706
707 const string queryS =
708 "SELECT *,\n"
709 " (fMeasurementTypeKey=4 AND fStart<='"+restart.GetAsStr()+"') AS `Reschedule`,\n"
710 " (fStart>'"+restart.GetAsStr()+"') AS `Following`,\n"
711 " (fMeasurementTypeKey=4 AND fStart BETWEEN '"+schedtime.GetAsStr()+"' AND '"+restart.GetAsStr()+"') AS `Delete`\n"
712 " FROM Schedule\n"
713 " WHERE fStart BETWEEN '"+sunset.GetAsStr()+"' AND '"+sunrise.GetAsStr()+"'\n"
714 " AND fMeasurementID=0\n"
715 " ORDER BY fStart ASC, fMeasurementID ASC";
716
717 const mysqlpp::StoreQueryResult resS = fConnection.query(queryS).store();
718
719 if (resS.num_rows()<2)
720 {
721 Error("Available schedule is too short to be evaluated.");
722 return false;
723 }
724
725 vector<string> list;
726
727 int32_t firstdata = -1;
728 int32_t reschedule = -1;
729 int32_t following = -1;
730 int32_t last_resch = -1;
731 for (size_t i=0; i<resS.num_rows(); i++)
732 {
733 const mysqlpp::Row &row = resS[i];
734
735 Out() << setw(2) << i << " | ";
736 Out() << row["Reschedule"] << " | ";
737 Out() << row["Following"] << " | ";
738 Out() << row["fScheduleID"] << " | ";
739 Out() << row["fStart"] << " | ";
740 if (bool(row["Delete"]))
741 {
742 Out() << " < delete > | ";
743 list.push_back((string)row["fScheduleID"]);
744 }
745 else
746 Out() << row["fLastUpdate"] << " | ";
747 Out() << row["fMeasurementID"] << " | ";
748 Out() << row["fUser"] << " | ";
749 Out() << row["fData"] << " | " << setw(4);
750 Out() << row["fSourceKey"] << " | ";
751 Out() << row["fMeasurementTypeKey"] << '\n';
752
753 if (bool(row["Reschedule"]))
754 reschedule = i;
755
756 if (bool(row["Reschedule"]) && !row["Delete"])
757 last_resch = i;
758
759 if (following==-1 && bool(row["Following"]))
760 following = i;
761
762 const uint16_t type = row["fMeasurementTypeKey"];
763 if (firstdata==-1 && type==4)
764 firstdata = i;
765 }
766
767 // --------------------------------------------------------------------
768
769 if (firstdata==-1)
770 {
771 Warn("No data run found.");
772 return false;
773 }
774
775 // --------------------------------------------------------------------
776
777 if (reschedule>=0)
778 Out() << "\nLast data run before restart at " << restart.GetAsStr() << ": idx=" << reschedule << " / ID=" << resS[reschedule]["fScheduleID"] << endl;
779
780 // --------------------------------------------------------------------
781
782 const Time first((string)resS[firstdata]["fStart"]); // First data run of the night
783 const Time last( (string)resS[resS.num_rows()-1]["fStart"]); // Last scheduled observation of the night
784
785 // --------------------------------------------------------------------
786
787 if (restart<=first)
788 {
789 Warn("ToO ends before first data taking... skipped.");
790 return false;
791 }
792 if (schedtime>=last)
793 {
794 Warn("ToO starts after data taking... skipped.");
795 return false;
796 }
797
798 // --------------------------------------------------------------------
799
800 if (schedtime<first)
801 {
802 Info("ToO starts before first data taking... rescheduling start time.");
803 schedtime = first;
804 }
805 if (restart>last)
806 {
807 Info("ToO ends after data taking... rescheduling end time!");
808 restart = last;
809 }
810
811 // --------------------------------------------------------------------
812
813 if (restart<schedtime+boost::posix_time::minutes(required))
814 {
815 Warn("Could not schedule more than the required "+to_string(required)+" minutes... skipped.");
816 return false;
817 }
818
819 // --------------------------------------------------------------------
820
821 if (following>=0)
822 {
823 const Time follow((string)resS[following]["fStart"]);
824 if (follow<restart+boost::posix_time::minutes(skip_time))
825 {
826 Info("Following observation would be less than "+to_string(skip_time)+" min... skipping rescheduled source.");
827 reschedule = -1;
828 }
829 }
830
831 // ====================================================================
832
833 if (!list.empty())
834 {
835 Out() << "\nDelete entries:\n";
836 for (const auto &l : list)
837 Out() << l << "\n";
838 Out() << endl;
839 }
840
841 // ====================================================================
842
843 if (source.key<0)
844 {
845 const double wobble_angle = GetWobbleAngle(ra, dec);
846
847 Out() << Time()-stopwatch << endl;
848
849 const string query =
850 "INSERT INTO Source\n"
851 " (fSourceName, fRightAscension, fDeclination, fWobbleAngle0, fWobbleAngle1, fSourceTypeKey, fIsToO) VALUES\n"
852 " ('"+name+"', "+to_string(ra/15.)+", "+to_string(dec)+", "+to_string(wobble_angle-90)+", "+to_string(wobble_angle+90)+", 1, 1)";
853
854 if (!fDryRun)
855 {
856 auto q = fConnection.query(query);
857 q.execute();
858 if (!q.info().empty())
859 Info(q.info());
860
861 source.key = q.insert_id();
862
863 Info(string(fDryRun?"[dry-run] ":"")+"The new source got the key "+to_string(source.key));
864 }
865 else
866 Out() << query << endl;
867 }
868
869 Out() << Time()-stopwatch << endl;
870
871 // ====================================================================
872
873 Info("Source will be scheduled.");
874
875 Out() << "New entries:\n";
876
877 vector<string> insert;
878
879 const bool ongoing = last_resch>=0 && source.key==uint16_t(resS[last_resch]["fSourceKey"]);
880
881 if (ongoing)
882 Out() << " | | | < ongoing > | 0 | | | " << setw(4) << source.key << " | 4\n";
883 else
884 {
885 Out() << " | auto | " << schedtime.GetAsStr() << " | < start > | 0 | ToO | " << (is_pointing?" nodrs ":"nodrs,grb") << " | " << setw(4) << source.key << " | 4\n";
886 insert.emplace_back("('"+schedtime.GetAsStr()+"',0,'ToO','"+(is_pointing?"nodrs:true":"nodrs:true,grb:true")+"',"+to_string(source.key)+",4)");
887 }
888
889 // --------------------------------------------------------------------
890
891 if (reschedule>=0 && restart!=last)
892 {
893 Out() << " | auto | " << restart.GetAsStr() << " | < end > | 0 | ToO | NULL | " << setw(4) << resS[reschedule]["fSourceKey"] << " | 4\n";
894
895 const string fData = (string)resS[reschedule]["fData"];
896 const string fKey = (string)resS[reschedule]["fSourceKey"];
897
898 const string data = resS[reschedule]["fData"] ? "'"+fData+"'" : "NULL";
899 insert.emplace_back("('"+restart.GetAsStr()+"',0,'ToO',"+data+","+fKey+",4)");
900 }
901
902 // ====================================================================
903
904 Out() << Time()-stopwatch << endl;
905
906 // ====================================================================
907
908 const string queryD = string("DELETE FROM Schedule WHERE fScheduleID IN (")+boost::algorithm::join(list, ",")+")";
909
910 const string queryI =
911 "INSERT INTO Schedule\n (fStart,fMeasurementID,fUser,fData,fSourceKey,fMeasurementTypeKey)\n"
912 "VALUES\n "+string(boost::algorithm::join(insert, ",\n "));
913
914 if (fDryRun)
915 {
916 Out() << "\n";
917 if (!list.empty())
918 Out() << queryD << "\n\n";
919 if (!insert.empty())
920 Out() << queryI << "\n\n";
921 Out() << flush;
922 return false;
923 }
924
925 // Empty interrupt to stop data taking as early as possible
926 // Triggers also RELOAD_SOURCES
927 if (!ongoing)
928 Dim::SendCommandNB("DIM_CONTROL/INTERRUPT", "prepare");
929
930 // ---------------------------------------------------------
931
932 fConnection.query("LOCK TABLES Schedule WRITE");
933
934 // ---------------------------------------------------------
935 if (!list.empty())
936 {
937 const mysqlpp::SimpleResult res = fConnection.query(queryD).execute();
938 Info(to_string(res.rows())+" row(s) deleted from Schedule.");
939 }
940
941 // ---------------------------------------------------------
942
943 if (!insert.empty())
944 {
945 auto q = fConnection.query(queryI);
946 q.execute();
947 if (!q.info().empty())
948 Info(q.info());
949 }
950 // ---------------------------------------------------------
951
952 fConnection.query("UNLOCK TABLES");
953
954 // ---------------------------------------------------------
955
956 if (!ongoing)
957 Dim::SendCommand("DIM_CONTROL/INTERRUPT", "reschedule");
958
959 ostringstream out;
960 out << Time()-stopwatch;
961 Info(out);
962
963 return true;
964 }
965
966 bool Schedule(const string &name, const double &ra, const double &dec, const CheckVisibility &check, const bool &is_pointing)
967 {
968 try
969 {
970 return ScheduleImp(name, ra, dec, check, is_pointing);
971 }
972 catch (const exception &e)
973 {
974 Error(string("SQL query failed: ")+e.what());
975 fConnection.disconnect();
976 return false;
977 }
978 }
979
980 // ========================================================================
981
982
983 // ========================================================================
984
985 bool CheckEventSize(size_t has, const char *name, size_t size)
986 {
987 if (has==size)
988 return true;
989
990 ostringstream msg;
991 msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
992 Fatal(msg);
993 return false;
994 }
995
996 int ScheduleGCN(const EventImp &evt)
997 {
998 if (!CheckEventSize(evt.GetSize(), "ScheduleGCN", 2+4+3*8))
999 return kSM_FatalError;
1000
1001 const ToO::DataGRB &grb = evt.Ref<ToO::DataGRB>();
1002
1003 const auto it = GCN::PaketTypes.find(grb.type);
1004
1005 if (it==GCN::PaketTypes.end())
1006 {
1007 Warn("Unknown Packet Type: "+to_string(grb.type));
1008 return GetCurrentState();
1009 }
1010
1011 const auto &paket = it->second;
1012
1013 ostringstream out;
1014 out << "Received: '" << paket.name << "' ID=" << grb.type << " NUM=" << grb.trigid << " RA=" << grb.ra/15. << "h DEC=" << grb.dec << "\u00b0 ERR=" << grb.err;
1015
1016 /*
1017 const std::vector<int16_t> typelist =
1018 {
1019 51, // INTEGRAL_POINTDIR
1020
1021 53, // INTEGRAL_WAKEUP
1022 54, // INTEGRAL_REFINED
1023 55, // INTEGRAL_OFFLINE
1024
1025 // 56, // INTEGRAL_WEAK
1026 // 59, // KONUS_LC
1027
1028 60, // SWIFT_BAT_GRB_ALERT
1029 61, // SWIFT_BAT_GRB_POS_ACK
1030 62, // SWIFT_BAT_GRB_POS_NACK
1031
1032 83, // SWIFT_POINTDIR
1033
1034 97, // SWIFT_BAT_QL_POS
1035
1036 100, // AGILE_GRB_WAKEUP
1037 101, // AGILE_GRB_GROUND
1038 102, // AGILE_GRB_REFINED
1039
1040 110, // FERMI_GBM_FLT_POS
1041 111, // FERMI_GBM_GND_POS
1042 112, // FERMI_GBM_LC
1043 115, // FERMI_GBM_TRANS
1044
1045 123, // FERMI_LAT_TRANS
1046 125, // FERMI_LAT_MONITOR
1047
1048 // 134, // MAXI_UNKNOWN
1049 // 135, // MAXI_KNOWN
1050 // 136, // MAXI_TEST
1051
1052 157, // AMON_ICECUBE_COINC
1053 158, // AMON_ICECUBE_HESE
1054
1055 169, // AMON_ICECUBE_EHE
1056 171, // HAWC_BURST_MONITOR
1057 173, // ICECUBE_GOLD
1058 174, // ICECUBE_BRONZE
1059 };
1060 */
1061
1062 Info(out);
1063 Info(paket.description);
1064
1065 const CheckVisibility check(grb.ra, grb.dec);
1066
1067 //Info(string("Source is")+(check.visible?" ":" NOT ")+"visible.");
1068
1069 Info(string("Sun altitude: ")+(check.valid_sun ?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.solarobj.fSunHrz.alt)+"\u00b0]");
1070 if (check.valid_sun)
1071 {
1072 Info(string("Moon distance: ")+(check.valid_moon ?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.moon_dist)+"\u00b0]");
1073 Info(string("Zenith angle: ")+(check.valid_zd ?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.position.zd)+"\u00b0]");
1074 Info(string("Current: ")+(check.valid_current ?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.current)+" \u00b5A]");
1075 Info(string("Rel. threshold: ")+(check.valid_threshold?"OK ":"failed")+" ["+Tools::Form("%5.1f", check.threshold)+"]");
1076 }
1077
1078 if (!check.visible)
1079 {
1080 Info("Source not observable... skipped.");
1081 return fConnection.connected() ? GetCurrentState() : ToO::State::kDisconnected;
1082 }
1083
1084 const string name = paket.instrument+"#"+to_string(grb.trigid);
1085 Info("Source is visible... name set to '"+name+"'");
1086
1087 /*
1088 * Swift: https://gcn.gsfc.nasa.gov/swift.html
1089 relevante Paket-Typen:
1090 60 BAT_Alert
1091 97 BAT_QL_Pos
1092 61 BAT_Pos
1093 62 BAT_Pos_Nack
1094 Es kann wohl vorkommen, dass ein Alert danach anders klassifiziert ist
1095 -> BAT_Trans (84) und es steht dass dann in der GRB notice eine catalog
1096 ID im comment steht. Da ist es mir noch nicht so ganz klar, woran man
1097 echte Alerts erkennt.
1098
1099 * Fermi: https://gcn.gsfc.nasa.gov/fermi.html
1100 relevante Paket-Typen:
1101 110 GBM_Alert P B B B&A
1102 111 GBM_Flt_Pos P B B B&A
1103 112 GBM_Gnd_Pos P B B B&A
1104 115 GBM_Final_Pos P B B B&A
1105 Bei GBM müssen wir usn überlegen wie wir es mit den Positionsupdates
1106 machen - das können auch mal ein paar Grad sein. siehe zB
1107 https://gcn.gsfc.nasa.gov/other/568833894.fermi
1108
1109 Wenn ich das richtig sehe, kommt bei GBM_Alert noch keine
1110 Position, sndern erst mit GBM_Flt_Pos (in dem Bsp kommt das 3 Sek
1111 später)
1112
1113 * INTEGRAL: https://gcn.gsfc.nasa.gov/integral.html
1114 wenn ich es richtig verstehe, sind die interessanten Alerts die WAKEUP
1115 (tw kommt ein Positionsupdate via OFFLINE oder REFINED). Es gibt auch
1116 noch WEAK, aber das sind scheinbar subthreshold alerts - würde ich
1117 jetzt erst mal weglassen. Im letzten Jahr gab es 8 WAKEUP alerts. 2017
1118 sogar nur 3, 2016 7, 2015 waren es einige mehr, aber das war V404_Cyg -
1119 in dem Fall steht dann aber ein Object-Name dabei - ansonsten steht as
1120 "Possbibly real GRB event" - zT steht auch, dass es coincident mit GBM
1121 events ist
1122
1123 * KONUS: https://gcn.gsfc.nasa.gov/konus.html
1124 Ist mir noch etwas unklar, was das genau ist - die schicken Lichtkurven
1125 von GRBs und Transients - muss man nochmal genauer schauen.
1126
1127 * MAXI: https://gcn.gsfc.nasa.gov/maxi.html
1128 wenn ich das richtig verstehe, sind das nicht nur GRBs -> müsste man
1129 nochmal genauer schauen bevor man da was implementiert
1130
1131 * AGILE: https://gcn.gsfc.nasa.gov/agile.html
1132 Wahrscheinlich eher nicht relevant, da steht was von 30min nach dem
1133 Burst kommt das 'Wakeup'. Die relevanten Paket-Typen wären 100
1134 (Wakeup_Pos), 101 (Ground_Pos) und 102 (Refined_Pos)
1135
1136 * AMON: https://gcn.gsfc.nasa.gov/amon.html
1137 sind bisher nur neutrinos - da macht MAGIC glaub ich schon automatic
1138 Follow-Up - müssen wir also kucken was Sinn macht
1139
1140 * Ligo-Virgo GW: https://gcn.gsfc.nasa.gov/lvc.html
1141 da ist natürlich die Error-Region groß - müsste man schauen, was man
1142 da will
1143
1144 Fazit: am relevantesten sind Fermi/GBM und Swift/BAT. Evtl könnte
1145 INTEGRAL noch Sinn machen und über AMON und LVC sollte man nachdenken,
1146 ob/was man haben will.
1147*/
1148
1149 const bool is_pointing = grb.type==51 || grb.type==83;
1150
1151 Schedule(name, grb.ra, grb.dec, check, is_pointing);
1152
1153 return fConnection.connected() ? GetCurrentState() : ToO::State::kDisconnected;
1154 //return GetCurrentState();
1155 }
1156
1157 int Enable()
1158 {
1159 return GetCurrentState()==ToO::State::kConnected ? ToO::State::kArmed : GetCurrentState();
1160 }
1161 int Disable()
1162 {
1163 return GetCurrentState()==ToO::State::kArmed ? ToO::State::kConnected : GetCurrentState();
1164 }
1165
1166 int Execute()
1167 {
1168 Time now;
1169 if (now>fKeepAliveDeadline)
1170 {
1171 static int ernum = 0;
1172 try
1173 {
1174 // Unfortunately, reconnecting [Takes about 1s] is
1175 // the only non-blocking way to ensure an open
1176 // connection. Ping would be nice but is blocking.
1177 fConnection = Database(fUri);
1178 ernum = 0;
1179 }
1180 catch (const mysqlpp::ConnectionFailed &e)
1181 {
1182 if (ernum!=e.errnum())
1183 Error(e.what());
1184 ernum = e.errnum();
1185 }
1186
1187 fKeepAliveDeadline += boost::posix_time::seconds(fKeepAliveInterval);
1188 }
1189
1190 if (!fConnection.connected())
1191 return ToO::State::kDisconnected;
1192
1193 if (fConnection.connected() && GetCurrentState()<=ToO::State::kDisconnected)
1194 return ToO::State::kConnected;
1195
1196 return GetCurrentState();
1197 }
1198
1199public:
1200 StateMachineToO(ostream &out=cout) : StateMachineDim(out, "SCHEDULER"),
1201 fConnection(""), fKeepAliveDeadline(Time())
1202 {
1203 AddStateName(ToO::State::kDisconnected, "Disconnected",
1204 "The Dim DNS is reachable, but the required subsystems are not available.");
1205
1206 AddStateName(ToO::State::kConnected, "Connected",
1207 "All needed subsystems are connected to their hardware, no action is performed.");
1208
1209 AddStateName(ToO::State::kArmed, "Armed",
1210 "All needed subsystems are connected to their hardware, scheduling in progress.");
1211
1212 AddEvent("GCN", "S:1;I:1;D:1;D:1;D:1")
1213 (bind(&StateMachineToO::ScheduleGCN, this, placeholders::_1))
1214 (""
1215 "|type[int16]:TypeID (see HeaderGCN.h)"
1216 "|trig[uint32]:TriggerID"
1217 "|ra[deg]:Right ascension"
1218 "|dec[deg]:Declination"
1219 "|err[deg]:Error radius");
1220
1221 AddEvent("START", "")
1222 (bind(&StateMachineToO::Enable, this/*, placeholders::_1*/))
1223 ("");
1224
1225 AddEvent("STOP", "")
1226 (bind(&StateMachineToO::Disable, this/*, placeholders::_1*/))
1227 ("");
1228 }
1229
1230 /*
1231 bool GetConfig(Configuration &conf, const string &name, const string &sub, uint16_t &rc)
1232 {
1233 if (conf.HasDef(name, sub))
1234 {
1235 rc = conf.GetDef<uint16_t>(name, sub);
1236 return true;
1237 }
1238
1239 Error("Neither "+name+"default nor "+name+sub+" found.");
1240 return false;
1241 }*/
1242
1243 int EvalOptions(Configuration &conf)
1244 {
1245 fVerbose = !conf.Get<bool>("quiet");
1246 fKeepAliveInterval = conf.Get<uint16_t>("keep-alive");
1247 fUri = conf.Get<string>("schedule-database");
1248 return -1;
1249 }
1250};
1251
1252
1253// ------------------------------------------------------------------------
1254
1255#include "Main.h"
1256
1257template<class T>
1258int RunShell(Configuration &conf)
1259{
1260 return Main::execute<T, StateMachineToO>(conf);
1261}
1262
1263void SetupConfiguration(Configuration &conf)
1264{
1265 po::options_description control("Scheduler");
1266 control.add_options()
1267 ("quiet,q", po_bool(), "")
1268 ("keep-alive", var<uint16_t>(uint16_t(300)), "Interval in seconds to ping (reconnect) the database server")
1269 ("schedule-database", var<string>(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
1270 ;
1271
1272 conf.AddOptions(control);
1273}
1274
1275void PrintUsage()
1276{
1277 cout <<
1278 "The scheduler program is able to schedule a new observation.\n"
1279 "\n"
1280 "Usage: scheduler [-c type] [OPTIONS]\n"
1281 " or: scheduler [OPTIONS]\n";
1282 cout << endl;
1283}
1284
1285void PrintHelp()
1286{
1287 Main::PrintHelp<StateMachineToO>();
1288}
1289
1290int main(int argc, const char* argv[])
1291{
1292 Configuration conf(argv[0]);
1293 conf.SetPrintUsage(PrintUsage);
1294 Main::SetupConfiguration(conf);
1295 SetupConfiguration(conf);
1296
1297 if (!conf.DoParse(argc, argv, PrintHelp))
1298 return 127;
1299
1300 if (!conf.Has("console"))
1301 return RunShell<LocalStream>(conf);
1302
1303 if (conf.Get<int>("console")==0)
1304 return RunShell<LocalShell>(conf);
1305 else
1306 return RunShell<LocalConsole>(conf);
1307
1308 return 0;
1309}
1310
Note: See TracBrowser for help on using the repository browser.