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

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