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

Last change on this file since 19479 was 19479, checked in by tbretz, 4 months ago
Only enter a potential new source key if an observation is really scheduled.
File size: 39.1 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        if (source_key<0)
787        {
788            const string query =
789                "INSERT INTO Source\n"
790                " (fSourceName, fRightAscension, fDeclination, fWobbleAngle0, fWobbleAngle1, fSourceTypeKey, fIsToO) VALUES\n"
791                " ('"+name+"', "+to_string(ra/15.)+", "+to_string(dec)+", "+to_string(wobble_angle-90)+", "+to_string(wobble_angle+90)+", 1, 1)";
792
793            if (!fDryRun)
794            {
795                auto q = fConnection.query(query);
796                q.execute();
797                Info(q.info());
798
799                source_key = q.insert_id();
800
801                Info(string(fDryRun?"[dry-run] ":"")+"The new source got the key "+to_string(source_key));
802            }
803            else
804                Out() << query << endl;
805
806        }
807
808        Out() << Time()-stopwatch << endl;
809
810        // ====================================================================
811
812        vector<string> insert;
813
814        Out() << "New entries:\n";
815        Out() << "           | auto  | " << schedtime.GetAsStr()     << " |       < start >     | 0 | ToO  | nodrs,grb | " << setw(4) << source_key << " | 4\n";
816
817        insert.emplace_back("('"+schedtime.GetAsStr()+"',0,'ToO','nodrs:true,grb:true',"+to_string(source_key)+",4)");
818
819        // --------------------------------------------------------------------
820
821        if (reschedule>=0 && restart!=last)
822        {
823            Out() << "           | auto  | " << restart.GetAsStr() << " |        < end >      | 0 | ToO  |   NULL    | " << setw(4) << resS[reschedule]["fSourceKey"] << " | 4\n";
824
825            const string fData = (string)resS[reschedule]["fData"];
826            const string fKey  = (string)resS[reschedule]["fSourceKey"];
827
828            const string data  = resS[reschedule]["fData"] ? "'"+fData+"'" : "NULL";
829            insert.emplace_back("('"+restart.GetAsStr()+"',0,'ToO',"+data+","+fKey+",4)");
830        }
831
832        // ====================================================================
833
834        Out() << Time()-stopwatch << endl;
835
836        // ====================================================================
837
838        const string queryD = string("DELETE FROM Schedule WHERE fScheduleID IN (")+boost::algorithm::join(list, ",")+")";
839
840        const string queryI =
841            "INSERT INTO Schedule\n (fStart,fMeasurementID,fUser,fData,fSoureKey,fMeasurementTypeKey)\n"
842            "VALUES\n "+string(boost::algorithm::join(insert, ",\n "));
843
844        if (fDryRun)
845        {
846            Out() << "\n";
847            if (!list.empty())
848                Out() << queryD << "\n\n";
849            if (!insert.empty())
850                Out() << queryI << "\n\n";
851            Out() << flush;
852            return false;
853        }
854
855        // Empty interrupt to stop data taking as early as possible
856        Dim::SendCommandNB("DIM_CONTROL/INTERRUPT");
857
858        // Reload sources as early as possible
859        Dim::SendCommandNB("DRIVE_CONTROL/RELOAD_SOURCES");
860
861        // Start pointing procedure as early as possible
862        //Dim::SendCommand("DRIVE_CONTROL/TRACK_WOBBLE", wobble, obs[sub].source);
863
864        // ---------------------------------------------------------
865
866        fConnection.query("LOCK TABLES Schedule WRITE");
867
868        // ---------------------------------------------------------
869        /*
870         if (!list.empty())
871         {
872         const mysqlpp::SimpleResult res = fConnection.query(queryD).execute();
873         Info(to_string(res.rows())+" row(s) deleted from Schedule.");
874         }
875
876         // ---------------------------------------------------------
877
878         if (!insert.empty())
879         {
880         auto q = fConnection.query(queryI);
881         q.execute();
882         Info(q.info());
883         }
884         */
885        // ---------------------------------------------------------
886
887        fConnection.query("UNLOCK TABLES");
888
889        // ---------------------------------------------------------
890
891        Dim::SendCommand("DIM_CONTROL/INTERRUPT", "reschedule");
892
893        ostringstream out;
894        out << Time()-stopwatch;
895        Info(out);
896
897        return true;
898    }
899
900    bool Schedule(const string &name, const double &ra, const double &dec)
901    {
902        try
903        {
904            return ScheduleImp(name, ra, dec);
905        }
906        catch (const exception &e)
907        {
908            Error(string("SQL query failed: ")+e.what());
909            fConnection.disconnect();
910            return false;
911        }
912    }
913
914    // ========================================================================
915
916
917    // ========================================================================
918
919    bool CheckEventSize(size_t has, const char *name, size_t size)
920    {
921        if (has==size)
922            return true;
923
924        ostringstream msg;
925        msg << name << " - Received event has " << has << " bytes, but expected " << size << ".";
926        Fatal(msg);
927        return false;
928    }
929
930    int ScheduleGCN(const EventImp &evt)
931    {
932        if (!CheckEventSize(evt.GetSize(), "ScheduleGCN", 2+4+3*8))
933            return kSM_FatalError;
934
935        const ToO::DataGRB &grb = evt.Ref<ToO::DataGRB>();
936
937        const GCN::PaketType_t *ptr=GCN::kTypes;
938        for (; ptr->type>=0 && ptr->type!=grb.type; ptr++);
939        if (ptr->type==-1)
940        {
941            Warn("Unknown Packet Type: "+to_string(ptr->type));
942            return GetCurrentState();
943        }
944
945        ostringstream out;
946        out << "Received: '" << ptr->name << "' ID=" << grb.type << " NUM=" << grb.trigid << " RA=" << grb.ra/15. << "h DEC=" << grb.dec << "\u00b0 ERR=" << grb.err;
947
948        Info(out);
949        Info(ptr->description);
950
951        const CheckVisibility check(grb.ra, grb.dec);
952
953        Info(string("Source is")+(check.visible?" ":" NOT ")+"visible.");
954
955        Info(string("Sun altitude:   ")+(check.valid_sun    ?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.solarobj.fSunHrz.alt)+"\u00b0]");
956        Info(string("Moon distance:  ")+(check.valid_moon   ?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.moon_dist)+"\u00b0]");
957        Info(string("Zenith angle:   ")+(check.valid_zd     ?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.position.zd)+"\u00b0]");
958        Info(string("Current:        ")+(check.valid_current?"OK    ":"failed")+" ["+Tools::Form("%5.1f", check.current)+" \u00b5A]");
959
960        const string name = ptr->instrument+"#"+to_string(grb.trigid);
961        Info("Source name set to '"+name+"'");
962
963        /*
964         * Swift: https://gcn.gsfc.nasa.gov/swift.html
965         relevante Paket-Typen:
966         60   BAT_Alert
967         97   BAT_QL_Pos
968         61   BAT_Pos
969         62   BAT_Pos_Nack
970         Es kann wohl vorkommen, dass ein Alert danach anders klassifiziert ist
971         -> BAT_Trans (84) und es steht dass dann in der GRB notice eine catalog
972         ID im comment steht. Da ist es mir noch nicht so ganz klar, woran man
973         echte Alerts erkennt.
974
975         * Fermi: https://gcn.gsfc.nasa.gov/fermi.html
976         relevante Paket-Typen:
977         110   GBM_Alert           P        B               B                B&A
978         111   GBM_Flt_Pos         P        B               B                B&A
979         112   GBM_Gnd_Pos         P        B               B                B&A
980         115   GBM_Final_Pos       P        B               B                B&A
981         Bei GBM müssen wir usn überlegen wie wir es mit den Positionsupdates
982         machen - das können auch mal ein paar Grad sein. siehe zB
983         https://gcn.gsfc.nasa.gov/other/568833894.fermi
984
985         Wenn ich das richtig sehe, kommt bei GBM_Alert noch keine
986         Position, sndern erst mit GBM_Flt_Pos (in dem Bsp kommt das 3 Sek
987         später)
988
989         * INTEGRAL: https://gcn.gsfc.nasa.gov/integral.html
990         wenn ich es richtig verstehe, sind die interessanten Alerts die WAKEUP
991         (tw kommt ein Positionsupdate via OFFLINE oder REFINED). Es gibt auch
992         noch WEAK, aber das sind scheinbar subthreshold alerts - würde ich
993         jetzt erst mal weglassen. Im letzten Jahr gab es 8 WAKEUP alerts. 2017
994         sogar nur 3, 2016 7, 2015 waren es einige mehr, aber das war V404_Cyg -
995         in dem Fall steht dann aber ein Object-Name dabei - ansonsten steht as
996         "Possbibly real GRB event" - zT steht auch, dass es coincident mit GBM
997         events ist
998
999         * KONUS: https://gcn.gsfc.nasa.gov/konus.html
1000         Ist mir noch etwas unklar, was das genau ist - die schicken Lichtkurven
1001         von GRBs und Transients - muss man nochmal genauer schauen.
1002
1003         * MAXI: https://gcn.gsfc.nasa.gov/maxi.html
1004         wenn ich das richtig verstehe, sind das nicht nur GRBs -> müsste man
1005         nochmal genauer schauen bevor man da was implementiert
1006
1007         * AGILE: https://gcn.gsfc.nasa.gov/agile.html
1008         Wahrscheinlich eher nicht relevant, da steht was von 30min nach dem
1009         Burst kommt das 'Wakeup'. Die relevanten Paket-Typen wären 100
1010         (Wakeup_Pos), 101 (Ground_Pos) und 102 (Refined_Pos)
1011
1012         * AMON: https://gcn.gsfc.nasa.gov/amon.html
1013         sind bisher nur neutrinos - da macht MAGIC glaub ich schon automatic
1014         Follow-Up - müssen wir also kucken was Sinn macht
1015
1016         * Ligo-Virgo GW: https://gcn.gsfc.nasa.gov/lvc.html
1017         da ist natürlich die Error-Region groß - müsste man schauen, was man
1018         da will
1019
1020         Fazit: am relevantesten sind Fermi/GBM und Swift/BAT. Evtl könnte
1021         INTEGRAL noch Sinn machen und über AMON und LVC sollte man nachdenken,
1022         ob/was man haben will.
1023*/
1024
1025        Schedule(name, grb.ra, grb.dec);
1026
1027        return fConnection.connected() ? ToO::State::kConnected : ToO::State::kDisconnected;
1028        //return GetCurrentState();
1029    }
1030
1031    int Execute()
1032    {
1033        Time now;
1034        if (now>fKeepAliveDeadline)
1035        {
1036            static int ernum = 0;
1037            try
1038            {
1039                // Unfortunately, reconnecting [Takes about 1s] is
1040                // the only non-blocking way to ensure an open
1041                // connection. Ping would be nice but is blocking.
1042                fConnection = Database(fUri);
1043                ernum = 0;
1044            }
1045            catch (const mysqlpp::ConnectionFailed &e)
1046            {
1047                if (ernum!=e.errnum())
1048                    Error(e.what());
1049                ernum = e.errnum();
1050            }
1051
1052            fKeepAliveDeadline += boost::posix_time::seconds(fKeepAliveInterval);
1053        }
1054
1055        return fConnection.connected() ? ToO::State::kConnected : ToO::State::kDisconnected;
1056    }
1057
1058public:
1059    StateMachineToO(ostream &out=cout) : StateMachineDim(out, "SCHEDULER"),
1060        fConnection(""), fKeepAliveDeadline(Time())
1061    {
1062        AddStateName(ToO::State::kDisconnected, "Disconnected",
1063                     "The Dim DNS is reachable, but the required subsystems are not available.");
1064
1065        AddStateName(ToO::State::kConnected, "Connected",
1066                     "All needed subsystems are connected to their hardware, no action is performed.");
1067
1068        AddEvent("GCN", "S:1;I:1;D:1;D:1;D:1")
1069            (bind(&StateMachineToO::ScheduleGCN, this, placeholders::_1))
1070            (""
1071             "|type[int16]:TypeID (see HeaderGCN.h)"
1072             "|num[uint32]:Right ascension"
1073             "|ra[deg]:Right ascension"
1074             "|dec[deg]:Declination"
1075             "|err[deg]:Error radius");
1076    }
1077
1078    /*
1079    bool GetConfig(Configuration &conf, const string &name, const string &sub, uint16_t &rc)
1080    {
1081        if (conf.HasDef(name, sub))
1082        {
1083            rc = conf.GetDef<uint16_t>(name, sub);
1084            return true;
1085        }
1086
1087        Error("Neither "+name+"default nor "+name+sub+" found.");
1088        return false;
1089    }*/
1090
1091    int EvalOptions(Configuration &conf)
1092    {
1093        fVerbose = !conf.Get<bool>("quiet");
1094        fDryRun = conf.Get<bool>("dry-run");
1095        fKeepAliveInterval = conf.Get<uint16_t>("keep-alive");
1096        fUri = conf.Get<string>("schedule-database");
1097        return -1;
1098    }
1099};
1100
1101
1102// ------------------------------------------------------------------------
1103
1104#include "Main.h"
1105
1106template<class T>
1107int RunShell(Configuration &conf)
1108{
1109    return Main::execute<T, StateMachineToO>(conf);
1110}
1111
1112void SetupConfiguration(Configuration &conf)
1113{
1114    po::options_description control("Scheduler");
1115    control.add_options()
1116        ("quiet,q", po_bool(), "")
1117        ("dry-run", po_bool(), "Switch off any alteration of the database")
1118        ("keep-alive", var<uint16_t>(uint16_t(300)), "Interval in seconds to ping (reconnect) the database server")
1119        ("schedule-database", var<string>(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
1120        ;
1121
1122    conf.AddOptions(control);
1123}
1124
1125void PrintUsage()
1126{
1127    cout <<
1128        "The scheduler program is able to schedule a new observation.\n"
1129        "\n"
1130        "Usage: scheduler [-c type] [OPTIONS]\n"
1131        "  or:  scheduler [OPTIONS]\n";
1132    cout << endl;
1133}
1134
1135void PrintHelp()
1136{
1137    Main::PrintHelp<StateMachineToO>();
1138}
1139
1140int main(int argc, const char* argv[])
1141{
1142    Configuration conf(argv[0]);
1143    conf.SetPrintUsage(PrintUsage);
1144    Main::SetupConfiguration(conf);
1145    SetupConfiguration(conf);
1146
1147    if (!conf.DoParse(argc, argv, PrintHelp))
1148        return 127;
1149
1150    if (!conf.Has("console"))
1151        return RunShell<LocalStream>(conf);
1152
1153    if (conf.Get<int>("console")==0)
1154        return RunShell<LocalShell>(conf);
1155    else
1156        return RunShell<LocalConsole>(conf);
1157
1158    return 0;
1159}
1160
Note: See TracBrowser for help on using the repository browser.