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

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