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

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