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

Last change on this file since 19438 was 19438, checked in by tbretz, 3 months ago
This is a new version which is supposed to be able schedule ToOs
File size: 38.2 KB
Line 
1#include "Prediction.h"
2
3#include <boost/algorithm/string/join.hpp>
4
5#include "Database.h"
6
7#include "HeadersToO.h"
8#include "HeadersGCN.h"
9
10#include "EventImp.h"
11#include "LocalControl.h"
12#include "StateMachineDim.h"
13
14#include "Dim.h"
15#include "tools.h"
16#include "Time.h"
17#include "Configuration.h"
18
19using namespace std;
20using namespace Nova;
21
22// -----------------------------------------------------------------------
23
24/*
25void SetupConfiguration(Configuration &conf);
26{
27    po::options_description control("Makeschedule");
28    control.add_options()
29        ("uri", var<string>(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
30        ;
31
32    // po::positional_options_description p;
33    // p.add("date", 1); // The first positional options
34
35    conf.AddOptions(control);
36    //conf.SetArgumentPositions(p);
37}
38
39void PrintUsage();
40{
41    cout <<
42        "makeschedule - Creates an automatic schedule\n"
43        "\n"
44        "Usage: makeschedule [yyyy-mm-dd]\n";
45    cout << endl;
46}
47
48void PrintHelp();
49{
50    cout << endl;
51}*/
52
53struct CheckVisibility
54{
55    // Input
56    float moon_min     =  10;
57    float moon_max     = 170;
58    float sun_max      = -12;
59    float zd_max       =  75;
60    float current_max  = 110;
61
62    // Output
63    Nova::SolarObjects solarobj;
64    Nova::ZdAzPosn position;
65
66    double moon_dist   = -1;
67    double current     = -1;
68
69    bool valid_zd      = false;
70    bool valid_current = false;
71    bool valid_sun     = false;
72    bool valid_moon    = false;
73
74    bool visible       = false;
75
76    void calc(const Nova::EquPosn &equ, const double &jd)
77    {
78        solarobj  = Nova::SolarObjects(jd);
79        position  = Nova::GetHrzFromEqu(equ, jd);
80        moon_dist = Nova::GetAngularSeparation(equ, solarobj.fMoonEqu);
81
82        current   = FACT::PredictI(solarobj, equ);
83
84        valid_moon    = moon_dist>moon_min && moon_dist<moon_max;
85        valid_zd      = position.zd<zd_max;
86        valid_sun     = solarobj.fSunHrz.alt<sun_max;
87        valid_current = current<current_max;
88
89        visible = valid_moon && valid_zd && valid_sun && valid_current;
90    }
91
92    CheckVisibility(const Nova::EquPosn &equ, const double &jd)
93    {
94        calc(equ, jd);
95    }
96
97    CheckVisibility(const Nova::EquPosn &equ)
98    {
99        calc(equ, Time().JD());
100    }
101
102    CheckVisibility(const double &ra, const double &dec, const double &jd)
103    {
104        Nova::EquPosn equ;
105        equ.ra  = ra;
106        equ.dec = dec;
107        calc(equ, jd);
108    }
109
110    CheckVisibility(const double &ra, const double &dec)
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    string   fUri;
414    Database fConnection;
415    uint16_t fVerbose;
416    bool     fDryRun;
417
418    Time     fKeepAliveDeadline;
419    uint16_t fKeepAliveInterval;
420
421    bool ScheduleImp(const string &name, const double &ra, const double &dec)
422    {
423        fDryRun = true;
424
425        Time stopwatch;
426
427        // This is just a desperate try which will most likely fail
428        if (!fConnection.connected())
429            fConnection.reconnect();
430
431        /*
432         +-----------------+---------------------+------+-----+---------+----------------+
433         | Field           | Type                | Null | Key | Default | Extra          |
434         +-----------------+---------------------+------+-----+---------+----------------+
435         | fSourceKEY      | int(11)             | NO   | PRI | NULL    | auto_increment |
436         | fSourceName     | varchar(30)         | NO   |     | NULL    |                |
437         | fRightAscension | double(10,6)        | NO   |     | NULL    |                |
438         | fDeclination    | double(10,6)        | NO   |     | NULL    |                |
439         | fEpochKEY       | tinyint(4)          | YES  |     | NULL    |                |
440         | fFlux           | float               | YES  |     | NULL    |                |
441         | fSlope          | float               | YES  |     | NULL    |                |
442         | fSourceTypeKey  | int(11)             | NO   | MUL | NULL    |                |
443         | fWobbleOffset   | float               | NO   |     | 0.6     |                |
444         | fWobbleAngle0   | int(11)             | NO   |     | 90      |                |
445         | fWobbleAngle1   | int(11)             | NO   |     | -90     |                |
446         | fMagnitude      | float(4,2)          | YES  |     | NULL    |                |
447         | fIsToO          | tinyint(3) unsigned | NO   |     | 0       |                |
448         +-----------------+---------------------+------+-----+---------+----------------+
449         */
450
451        const double min_dist = 0.1;
452        const double wobble_offset = 0.6;
453        const double camera_radius = 2.3;
454        const double magnitude_max = 4.5;
455
456        double wobble_angle  = 0;
457
458        /*
459         Mag Cnt
460         -2  1
461         -1  3
462          0  11
463          1  33
464          2  121
465          3  321
466          4  871
467          5  1364
468          6  404
469          7  12
470        */
471
472        // The wobble position lay in the plane whihc is normal the to the plane source-center-star
473        // and goes through
474
475        const string query0 =
476            "SELECT fSourceKEY, fRightAscension, fDeclination, fMagnitude,\n"
477            " ADIST(fDeclination, fRightAscension*15, "+to_string(dec)+", "+to_string(ra)+") AS Dist\n"
478            " FROM Source\n"
479            " WHERE (fSourceTypeKey=2 OR fSourceTypeKey=3) AND fMagnitude<"+to_string(magnitude_max)+"\n"
480            " HAVING Dist<"+to_string(camera_radius+wobble_offset)+"\n"
481            " ORDER BY fMagnitude ASC";
482
483        Out() << query0 << endl;
484
485        const mysqlpp::StoreQueryResult res0 = fConnection.query(query0).store();
486
487        Out() << Time()-stopwatch << endl;
488
489        Info("Found "+to_string(res0.num_rows())+" stars in the camera field-of-view with magnitude less than "+to_string(magnitude_max));
490
491        for (size_t i=0; i<::min<size_t>(10, res0.num_rows()); i++)
492        {
493            const mysqlpp::Row &row = res0[i];
494
495            // TVector3 souce, star;
496            // source.SetMagThetaPhi(1, rad(90-dec), rad(ra));
497            // star.SetMagThetaPhi(  1, rad(90-dec), rad(ra));
498            //
499            // star.RotateZ(  -source.Phi());
500            // source.RotateZ(-source.Phi());
501            //
502            // star.RotateY(  180-source.Theta());
503            // source.RotateY(180-source.Theta());
504            //
505            // rho = star.Phi();
506
507            const double rad = M_PI/180;
508
509            const double dec0 = rad * dec;
510            const double ra0  = rad * ra;
511
512            const double dec1 = rad * row["fDeclination"];
513            const double ra1  = rad * row["fRightAscension"] * 15;
514
515            const double s2 = sin(ra0-ra1);
516            const double c2 = cos(ra0-ra1);
517
518            const double s0 = cos(dec0);
519            const double c0 = sin(dec0);
520
521            const double c1 = cos(dec1);
522            const double s1 = sin(dec1);
523
524            const double k0 =           -s2*c1;
525            const double k1 = s0*s1 - c0*c2*c1;
526
527            const double rho = atan(k0/k1) / rad; // atan2(k0, k1)/rad
528
529            if (i==0)
530                wobble_angle = rho;
531
532            Info(" "+Tools::Form("Mag=%5.2f  Dist=%5.1f  Phi=%6.1f", (double)row["fMagnitude"], (double)row["Dist"], rho));
533        }
534
535        if (res0.num_rows())
536        {
537            Info("The brightest star (M="+string(res0[0]["fMagnitude"])+") at distance is visible at a wobble angle of "+Tools::Form("%.2f", wobble_angle)+"\u00b0");
538            Info("Wobble angles determined as "+Tools::Form("%.2f", wobble_angle-90)+"\u00b0 and "+Tools::Form("%.2f", wobble_angle+90)+"\u000b");
539        }
540        else
541        {
542            Info("Using default wobble angles.");
543        }
544
545        const string query1 =
546            "SELECT fSourceKey, fSourceName\n"
547            " FROM Source\n"
548            " WHERE ADIST(fDeclination, fRightAscension*15, "+to_string(dec)+", "+to_string(ra)+")<"+to_string(min_dist)+"\n"
549            " ORDER BY fSourceKey ASC";
550
551        const mysqlpp::StoreQueryResult res1 = fConnection.query(query1).store();
552
553        if (res1.num_rows())
554        {
555            Warn("The following sources in the source table have a distance less than "+to_string(min_dist)+"\u00b0");
556
557            for (size_t i=0; i<res1.num_rows(); i++)
558            {
559                const mysqlpp::Row &row = res1[i];
560                Warn(" "+string(row["fSourceName"])+" ["+to_string(uint32_t(row["fSourceKey"]))+"]");
561            }
562        }
563
564        Out() << Time()-stopwatch << endl;
565
566        int32_t source_key = -1;
567
568        const string query2 =
569            "SELECT fSourceKey FROM Source WHERE fSourceName='"+name+"'";
570
571        const mysqlpp::StoreQueryResult res2 = fConnection.query(query2).store();
572
573        if (res2.num_rows())
574        {
575            source_key = res2[0]["fSourceKey"];
576            Info("A source with the same name (key="+to_string(source_key)+") was found in the Source table.");
577        }
578
579        if (source_key<0)
580        {
581            const string query =
582                "INSERT INTO Source\n"
583                " (fSourceName, fRightAscension, fDeclination, fWobbleAngle0, fWobbleAngle1, fSourceTypeKey, fIsToO) VALUES\n"
584                " ('"+name+"', "+to_string(ra/15.)+", "+to_string(dec)+", "+to_string(wobble_angle-90)+", "+to_string(wobble_angle+90)+", 1, 1)";
585
586            if (!fDryRun)
587            {
588                auto q = fConnection.query(query);
589                q.execute();
590                Info(q.info());
591
592                source_key = q.insert_id();
593
594                Info(string(fDryRun?"[dry-run] ":"")+"The new source got the key "+to_string(source_key));
595            }
596            else
597                Out() << query << endl;
598
599        }
600
601        Out() << Time()-stopwatch << endl;
602
603        /*
604         +---------------------+--------------+------+-----+-------------------+-----------------------------+
605         | Field               | Type         | Null | Key | Default           | Extra                       |
606         +---------------------+--------------+------+-----+-------------------+-----------------------------+
607         | fScheduleID         | int(11)      | NO   | PRI | NULL              | auto_increment              |
608         | fStart              | datetime     | NO   | MUL | NULL              |                             |
609         | fLastUpdate         | timestamp    | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
610         | fMeasurementID      | int(11)      | NO   |     | NULL              |                             |
611         | fUser               | varchar(32)  | NO   |     | NULL              |                             |
612         | fData               | varchar(256) | YES  |     | NULL              |                             |
613         | fSourceKey          | smallint(6)  | YES  |     | NULL              |                             |
614         | fMeasurementTypeKey | tinyint(4)   | NO   |     | NULL              |                             |
615         +---------------------+--------------+------+-----+-------------------+-----------------------------+
616         */
617
618        const uint16_t contingency =   1; // The contingency to delete earlier entries from the Table
619        const uint16_t duration    =  60; // The target duration of the ToO
620        const uint16_t required    =  20; // If the ToO would be less than required, skip_it
621        const uint16_t skip_time   =  20; // If following observation would be less than skip_time, skip it
622
623        if (duration<required)
624        {
625            Error("Requested duration is smaller than required time.");
626            return false;
627        }
628
629        const Time now;
630
631        const Time sunset  = now.GetPrevSunSet();
632        const Time sunrise = now.GetNextSunRise();
633
634        if (sunset>sunrise || sunrise>sunset+boost::posix_time::hours(24))
635        {
636            Error("Unable to schedule a ToO during daytime.");
637            return false;
638        }
639
640        // Safety margin
641        Time schedtime = now-boost::posix_time::seconds(contingency);
642        Time restart   = now+boost::posix_time::minutes(duration);
643
644        Out() << '\n';
645        Out() << "Contingency:   " << contingency << " s\n";
646        Out() << "Start of ToO:  " << now.GetAsStr() << '\n';
647        Out() << "End   of ToO:  " << restart.GetAsStr() << '\n';
648        Out() << "Duration:      " << duration << " min\n\n";
649
650        Out() << "Schedule:\n";
651
652        const string queryS =
653            "SELECT *,\n"
654            " (fMeasurementTypeKey=4 AND fStart<='"+restart.GetAsStr()+"') AS `Reschedule`,\n"
655            " (fStart>'"+restart.GetAsStr()+"') AS `Following`,\n"
656            " (fMeasurementTypeKey=4 AND fStart BETWEEN '"+schedtime.GetAsStr()+"' AND '"+restart.GetAsStr()+"') AS `Delete`\n"
657            " FROM Schedule\n"
658            " WHERE fStart BETWEEN '"+sunset.GetAsStr()+"' AND '"+sunrise.GetAsStr()+"'\n"
659            " AND fMeasurementID=0\n"
660            " ORDER BY fStart ASC, fMeasurementID ASC";
661
662        const mysqlpp::StoreQueryResult resS = fConnection.query(queryS).store();
663
664        if (resS.num_rows()<2)
665        {
666            Error("Available schedule is too short to be evaluated.");
667            return false;
668        }
669
670        vector<string> list;
671
672        int32_t firstdata  = -1;
673        int32_t reschedule = -1;
674        int32_t following  = -1;
675        for (size_t i=0; i<resS.num_rows(); i++)
676        {
677            const mysqlpp::Row &row = resS[i];
678
679            Out() << setw(2) << i << " | ";
680            Out() << row["Reschedule"]     << " | ";
681            Out() << row["Following"]      << " | ";
682            Out() << row["fScheduleID"]    << " | ";
683            Out() << row["fStart"]         << " | ";
684            if (bool(row["Delete"]))
685            {
686                Out() << "     < delete >     | ";
687                list.push_back((string)row["fScheduleID"]);
688            }
689            else
690                Out() << row["fLastUpdate"]    << " | ";
691            Out() << row["fMeasurementID"] << " | ";
692            Out() << row["fUser"]          << " | ";
693            Out() << row["fData"]          << " | " << setw(4);
694            Out() << row["fSourceKey"]     << " | ";
695            Out() << row["fMeasurementTypeKey"] << '\n';
696
697            if (bool(row["Reschedule"]))
698                reschedule = i;
699
700            if (following==-1 && bool(row["Following"]))
701                following = i;
702
703            const uint16_t type = row["fMeasurementTypeKey"];
704            if (firstdata==-1 && type==4)
705                firstdata = i;
706        }
707
708        // --------------------------------------------------------------------
709
710        if (firstdata==-1)
711        {
712            Warn("No data run found.");
713            return false;
714        }
715
716        // --------------------------------------------------------------------
717
718        if (reschedule>=0)
719            Out() << "\nLast data run before restart at " << restart.GetAsStr() << ": idx=" << reschedule << " / ID=" << resS[reschedule]["fScheduleID"] << endl;
720
721        // --------------------------------------------------------------------
722
723        const Time first((string)resS[firstdata]["fStart"]);          // First data run of the night
724        const Time last( (string)resS[resS.num_rows()-1]["fStart"]);  // Last scheduled observation of the night
725
726        // --------------------------------------------------------------------
727
728        if (restart<=first)
729        {
730            Warn("ToO ends before first data taking... skipped.");
731            return false;
732        }
733        if (schedtime>=last)
734        {
735            Warn("ToO starts after data taking... skipped.");
736            return false;
737        }
738
739        // --------------------------------------------------------------------
740
741        if (schedtime<first)
742        {
743            Info("ToO starts before first data taking... rescheduling start time.");
744            schedtime = first;
745        }
746        if (restart>last)
747        {
748            Info("ToO ends after data taking... rescheduling end time!");
749            restart = last;
750        }
751
752        // --------------------------------------------------------------------
753
754        if (restart<schedtime+boost::posix_time::minutes(required))
755        {
756            Warn("Could not schedule more than the required "+to_string(required)+" minutes... skipped.");
757            return false;
758        }
759
760        // --------------------------------------------------------------------
761
762        if (following>=0)
763        {
764            const Time follow((string)resS[following]["fStart"]);
765            if (follow<restart+boost::posix_time::minutes(skip_time))
766            {
767                Info("Following observation would be less than "+to_string(skip_time)+" min... skipping rescheduled source.");
768                reschedule = -1;
769            }
770        }
771
772        // ====================================================================
773
774        if (!list.empty())
775        {
776            Out() << "\nDelete entries:\n";
777            for (const auto &l : list)
778                Out() << l << "\n";
779            Out() << endl;
780        }
781
782        // ====================================================================
783
784        Info("Source will be scheduled.");
785
786        vector<string> insert;
787
788        Out() << "New entries:\n";
789        Out() << "           | auto  | " << schedtime.GetAsStr()     << " |       < start >     | 0 | ToO  | nodrs,grb | " << setw(4) << source_key << " | 4\n";
790
791        insert.emplace_back("('"+schedtime.GetAsStr()+"',0,'ToO','nodrs:true,grb:true',"+to_string(source_key)+",4)");
792
793        // --------------------------------------------------------------------
794
795        if (reschedule>=0 && restart!=last)
796        {
797            Out() << "           | auto  | " << restart.GetAsStr() << " |        < end >      | 0 | ToO  |   NULL    | " << setw(4) << resS[reschedule]["fSourceKey"] << " | 4\n";
798
799            const string fData = (string)resS[reschedule]["fData"];
800            const string fKey  = (string)resS[reschedule]["fSourceKey"];
801
802            const string data  = resS[reschedule]["fData"] ? "'"+fData+"'" : "NULL";
803            insert.emplace_back("('"+restart.GetAsStr()+"',0,'ToO',"+data+","+fKey+",4)");
804        }
805
806        // ====================================================================
807
808        Out() << Time()-stopwatch << endl;
809
810        // ====================================================================
811
812        const string queryD = string("DELETE FROM Schedule WHERE fScheduleID IN (")+boost::algorithm::join(list, ",")+")";
813
814        const string queryI =
815            "INSERT INTO Schedule\n (fStart,fMeasurementID,fUser,fData,fSoureKey,fMeasurementTypeKey)\n"
816            "VALUES\n "+string(boost::algorithm::join(insert, ",\n "));
817
818        if (fDryRun)
819        {
820            Out() << "\n";
821            if (!list.empty())
822                Out() << queryD << "\n\n";
823            if (!insert.empty())
824                Out() << queryI << "\n\n";
825            Out() << flush;
826            return false;
827        }
828
829        // Empty interrupt to stop data taking as early as possible
830        Dim::SendCommandNB("DIM_CONTROL/INTERRUPT");
831
832        // Reload sources as early as possible
833        Dim::SendCommandNB("DRIVE_CONTROL/RELOAD_SOURCES");
834
835        // Start pointing procedure as early as possible
836        //Dim::SendCommand("DRIVE_CONTROL/TRACK_WOBBLE", wobble, obs[sub].source);
837
838        // ---------------------------------------------------------
839
840        fConnection.query("LOCK TABLES Schedule WRITE");
841
842        // ---------------------------------------------------------
843        /*
844         if (!list.empty())
845         {
846         const mysqlpp::SimpleResult res = fConnection.query(queryD).execute();
847         Info(to_string(res.rows())+" row(s) deleted from Schedule.");
848         }
849
850         // ---------------------------------------------------------
851
852         if (!insert.empty())
853         {
854         auto q = fConnection.query(queryI);
855         q.execute();
856         Info(q.info());
857         }
858         */
859        // ---------------------------------------------------------
860
861        fConnection.query("UNLOCK TABLES");
862
863        // ---------------------------------------------------------
864
865        Dim::SendCommand("DIM_CONTROL/INTERRUPT", "reschedule");
866
867        ostringstream out;
868        out << Time()-stopwatch;
869        Info(out);
870
871        return true;
872    }
873
874    bool Schedule(const string &name, const double &ra, const double &dec)
875    {
876        try
877        {
878            return ScheduleImp(name, ra, dec);
879        }
880        catch (const exception &e)
881        {
882            Error(string("SQL query failed: ")+e.what());
883            fConnection.disconnect();
884            return false;
885        }
886    }
887
888    // ========================================================================
889
890
891    // ========================================================================
892
893    bool CheckEventSize(size_t has, const char *name, size_t size)
894    {
895        if (has==size)
896            return true;
897
898        ostringstream msg;
899        msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
900        Fatal(msg);
901        return false;
902    }
903
904    int ScheduleGCN(const EventImp &evt)
905    {
906        if (!CheckEventSize(evt.GetSize(), "ScheduleGCN", 2+4+3*8))
907            return kSM_FatalError;
908
909        const ToO::DataGRB &grb = evt.Ref<ToO::DataGRB>();
910
911        const GCN::PaketType_t *ptr=GCN::kTypes;
912        for (; ptr->type>=0 && ptr->type!=grb.type; ptr++);
913        if (ptr->type==-1)
914        {
915            Warn("Unknown Packet Type: "+to_string(ptr->type));
916            return GetCurrentState();
917        }
918
919        ostringstream out;
920        out << "Received: '" << ptr->name << "' ID=" << grb.type << " NUM=" << grb.trigid << " RA=" << grb.ra/15. << "h DEC=" << grb.dec << "\u00b0 ERR=" << grb.err;
921
922        Info(out);
923        Info(ptr->description);
924
925        const CheckVisibility check(grb.ra, grb.dec);
926
927        Info(string("Source is")+(check.visible?" ":" NOT ")+"visible.");
928
929        Info(string("Sun altitude:   ")+(check.valid_sun    ?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.solarobj.fSunHrz.alt)+"\u00b0]");
930        Info(string("Moon distance:  ")+(check.valid_moon   ?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.moon_dist)+"\u00b0]");
931        Info(string("Zenith angle:   ")+(check.valid_zd     ?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.position.zd)+"\u00b0]");
932        Info(string("Current:        ")+(check.valid_current?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.current)+" \u00b5A]");
933
934        const string name = ptr->instrument+"#"+to_string(grb.trigid);
935        Info("Source name set to '"+name+"'");
936
937        /*
938         * Swift: https://gcn.gsfc.nasa.gov/swift.html
939         relevante Paket-Typen:
940         60   BAT_Alert
941         97   BAT_QL_Pos
942         61   BAT_Pos
943         62   BAT_Pos_Nack
944         Es kann wohl vorkommen, dass ein Alert danach anders klassifiziert ist
945         -> BAT_Trans (84) und es steht dass dann in der GRB notice eine catalog
946         ID im comment steht. Da ist es mir noch nicht so ganz klar, woran man
947         echte Alerts erkennt.
948
949         * Fermi: https://gcn.gsfc.nasa.gov/fermi.html
950         relevante Paket-Typen:
951         110   GBM_Alert           P        B               B                B&A
952         111   GBM_Flt_Pos         P        B               B                B&A
953         112   GBM_Gnd_Pos         P        B               B                B&A
954         115   GBM_Final_Pos       P        B               B                B&A
955         Bei GBM müssen wir usn überlegen wie wir es mit den Positionsupdates
956         machen - das können auch mal ein paar Grad sein. siehe zB
957         https://gcn.gsfc.nasa.gov/other/568833894.fermi
958
959         Wenn ich das richtig sehe, kommt bei GBM_Alert noch keine
960         Position, sndern erst mit GBM_Flt_Pos (in dem Bsp kommt das 3 Sek
961         später).
962
963         * INTEGRAL: https://gcn.gsfc.nasa.gov/integral.html
964         wenn ich es richtig verstehe, sind die interessanten Alerts die WAKEUP
965         (tw kommt ein Positionsupdate via OFFLINE oder REFINED). Es gibt auch
966         noch WEAK, aber das sind scheinbar subthreshold alerts - würde ich
967         jetzt erst mal weglassen. Im letzten Jahr gab es 8 WAKEUP alerts. 2017
968         sogar nur 3, 2016 7, 2015 waren es einige mehr, aber das war V404_Cyg -
969         in dem Fall steht dann aber ein Object-Name dabei - ansonsten steht as
970         "Possbibly real GRB event" - zT steht auch, dass es coincident mit GBM
971         events ist
972
973         * KONUS: https://gcn.gsfc.nasa.gov/konus.html
974         Ist mir noch etwas unklar, was das genau ist - die schicken Lichtkurven
975         von GRBs und Transients - muss man nochmal genauer schauen.
976
977         * MAXI: https://gcn.gsfc.nasa.gov/maxi.html
978         wenn ich das richtig verstehe, sind das nicht nur GRBs -> müsste man
979         nochmal genauer schauen bevor man da was implementiert
980
981         * AGILE: https://gcn.gsfc.nasa.gov/agile.html
982         Wahrscheinlich eher nicht relevant, da steht was von 30min nach dem
983         Burst kommt das 'Wakeup'. Die relevanten Paket-Typen wären 100
984         (Wakeup_Pos), 101 (Ground_Pos) und 102 (Refined_Pos)
985
986         * AMON: https://gcn.gsfc.nasa.gov/amon.html
987         sind bisher nur neutrinos - da macht MAGIC glaub ich schon automatic
988         Follow-Up - müssen wir also kucken was Sinn macht
989
990         * Ligo-Virgo GW: https://gcn.gsfc.nasa.gov/lvc.html
991         da ist natürlich die Error-Region groß - müsste man schauen, was man
992         da will
993
994         Fazit: am relevantesten sind Fermi/GBM und Swift/BAT. Evtl könnte
995         INTEGRAL noch Sinn machen und über AMON und LVC sollte man nachdenken,
996         ob/was man haben will.
997*/
998
999        Schedule(name, grb.ra, grb.dec);
1000
1001        return fConnection.connected() ? ToO::State::kConnected : ToO::State::kDisconnected;
1002        //return GetCurrentState();
1003    }
1004
1005    int Execute()
1006    {
1007        Time now;
1008        if (now>fKeepAliveDeadline)
1009        {
1010            static int ernum = 0;
1011            try
1012            {
1013                // Unfortunately, reconnecting [Takes about 1s] is
1014                // the only non-blocking way to ensure an open
1015                // connection. Ping would be nice but is blocking.
1016                fConnection = Database(fUri);
1017                ernum = 0;
1018            }
1019            catch (const mysqlpp::ConnectionFailed &e)
1020            {
1021                if (ernum!=e.errnum())
1022                    Error(e.what());
1023                ernum = e.errnum();
1024            }
1025
1026            fKeepAliveDeadline += boost::posix_time::seconds(fKeepAliveInterval);
1027        }
1028
1029        return fConnection.connected() ? ToO::State::kConnected : ToO::State::kDisconnected;
1030    }
1031
1032public:
1033    StateMachineToO(ostream &out=cout) : StateMachineDim(out, "SCHEDULER"),
1034        fConnection(""), fKeepAliveDeadline(Time())
1035    {
1036        AddStateName(ToO::State::kDisconnected, "Disconnected",
1037                     "The Dim DNS is reachable, but the required subsystems are not available.");
1038
1039        AddStateName(ToO::State::kConnected, "Connected",
1040                     "All needed subsystems are connected to their hardware, no action is performed.");
1041
1042        AddEvent("GCN", "S:1;I:1;D:1;D:1;D:1")
1043            (bind(&StateMachineToO::ScheduleGCN, this, placeholders::_1))
1044            (""
1045             "|type[int16]:TypeID (see HeaderGCN.h)"
1046             "|num[uint32]:Right ascension"
1047             "|ra[double]:Right ascension"
1048             "|dec[double]:Declination"
1049             "|err[double]:Declination");
1050    }
1051
1052    /*
1053    bool GetConfig(Configuration &conf, const string &name, const string &sub, uint16_t &rc)
1054    {
1055        if (conf.HasDef(name, sub))
1056        {
1057            rc = conf.GetDef<uint16_t>(name, sub);
1058            return true;
1059        }
1060
1061        Error("Neither "+name+"default nor "+name+sub+" found.");
1062        return false;
1063    }*/
1064
1065    int EvalOptions(Configuration &conf)
1066    {
1067        fVerbose = !conf.Get<bool>("quiet");
1068        fDryRun = conf.Get<bool>("dry-run");
1069        fKeepAliveInterval = conf.Get<uint16_t>("keep-alive");
1070        fUri = conf.Get<string>("schedule-database");
1071        return -1;
1072    }
1073};
1074
1075
1076// ------------------------------------------------------------------------
1077
1078#include "Main.h"
1079
1080template<class T>
1081int RunShell(Configuration &conf)
1082{
1083    return Main::execute<T, StateMachineToO>(conf);
1084}
1085
1086void SetupConfiguration(Configuration &conf)
1087{
1088    po::options_description control("Scheduler");
1089    control.add_options()
1090        ("quiet,q", po_bool(), "")
1091        ("dry-run", po_bool(), "Switch off any alteration of the database")
1092        ("keep-alive", var<uint16_t>(uint16_t(300)), "Interval in seconds to ping (reconnect) the database server")
1093        ("schedule-database", var<string>(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
1094        ;
1095
1096    conf.AddOptions(control);
1097}
1098
1099void PrintUsage()
1100{
1101    cout <<
1102        "The scheduler program is able to schedule a new observation.\n"
1103        "\n"
1104        "Usage: scheduler [-c type] [OPTIONS]\n"
1105        "  or:  scheduler [OPTIONS]\n";
1106    cout << endl;
1107}
1108
1109void PrintHelp()
1110{
1111    Main::PrintHelp<StateMachineToO>();
1112}
1113
1114int main(int argc, const char* argv[])
1115{
1116    Configuration conf(argv[0]);
1117    conf.SetPrintUsage(PrintUsage);
1118    Main::SetupConfiguration(conf);
1119    SetupConfiguration(conf);
1120
1121    if (!conf.DoParse(argc, argv, PrintHelp))
1122        return 127;
1123
1124    if (!conf.Has("console"))
1125        return RunShell<LocalStream>(conf);
1126
1127    if (conf.Get<int>("console")==0)
1128        return RunShell<LocalShell>(conf);
1129    else
1130        return RunShell<LocalConsole>(conf);
1131
1132    return 0;
1133}
1134
Note: See TracBrowser for help on using the repository browser.