1 /*
2     SPDX-FileCopyrightText: 2015 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4     DBus calls from GSoC 2015 Ekos Scheduler project:
5     SPDX-FileCopyrightText: 2015 Daniel Leu <daniel_mihai.leu@cti.pub.ro>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #pragma once
11 
12 #include "ui_scheduler.h"
13 #include "ekos/align/align.h"
14 #include "indi/indiweather.h"
15 #include "schedulerjob.h"
16 
17 #include <lilxml.h>
18 
19 #include <QProcess>
20 #include <QTime>
21 #include <QTimer>
22 #include <QUrl>
23 #include <QtDBus>
24 
25 #include <cstdint>
26 
27 class QProgressIndicator;
28 
29 class GeoLocation;
30 class SchedulerJob;
31 class SkyObject;
32 class KConfigDialog;
33 class TestSchedulerUnit;
34 
35 class TestEkosSchedulerOps;
36 
37 namespace Ekos
38 {
39 
40 class SequenceJob;
41 
42 /**
43  * @brief The Ekos scheduler is a simple scheduler class to orchestrate automated multi object observation jobs.
44  * @author Jasem Mutlaq
45  * @version 1.2
46  */
47 class Scheduler : public QWidget, public Ui::Scheduler
48 {
49         Q_OBJECT
50         Q_CLASSINFO("D-Bus Interface", "org.kde.kstars.Ekos.Scheduler")
51         Q_PROPERTY(Ekos::SchedulerState status READ status NOTIFY newStatus)
52         Q_PROPERTY(QStringList logText READ logText NOTIFY newLog)
53         Q_PROPERTY(QString profile READ profile WRITE setProfile)
54 
55     public:
56         typedef enum { EKOS_IDLE, EKOS_STARTING, EKOS_STOPPING, EKOS_READY } EkosState;
57         typedef enum { INDI_IDLE, INDI_CONNECTING, INDI_DISCONNECTING, INDI_PROPERTY_CHECK, INDI_READY } INDIState;
58         typedef enum
59         {
60             STARTUP_IDLE,
61             STARTUP_SCRIPT,
62             STARTUP_UNPARK_DOME,
63             STARTUP_UNPARKING_DOME,
64             STARTUP_UNPARK_MOUNT,
65             STARTUP_UNPARKING_MOUNT,
66             STARTUP_UNPARK_CAP,
67             STARTUP_UNPARKING_CAP,
68             STARTUP_ERROR,
69             STARTUP_COMPLETE
70         } StartupState;
71         typedef enum
72         {
73             SHUTDOWN_IDLE,
74             SHUTDOWN_PARK_CAP,
75             SHUTDOWN_PARKING_CAP,
76             SHUTDOWN_PARK_MOUNT,
77             SHUTDOWN_PARKING_MOUNT,
78             SHUTDOWN_PARK_DOME,
79             SHUTDOWN_PARKING_DOME,
80             SHUTDOWN_SCRIPT,
81             SHUTDOWN_SCRIPT_RUNNING,
82             SHUTDOWN_ERROR,
83             SHUTDOWN_COMPLETE
84         } ShutdownState;
85         typedef enum
86         {
87             PARKWAIT_IDLE,
88             PARKWAIT_PARK,
89             PARKWAIT_PARKING,
90             PARKWAIT_PARKED,
91             PARKWAIT_UNPARK,
92             PARKWAIT_UNPARKING,
93             PARKWAIT_UNPARKED,
94             PARKWAIT_ERROR
95         } ParkWaitStatus;
96 
97         /** @brief options what should happen if an error or abort occurs */
98         typedef enum
99         {
100             ERROR_DONT_RESTART,
101             ERROR_RESTART_AFTER_TERMINATION,
102             ERROR_RESTART_IMMEDIATELY
103         } ErrorHandlingStrategy;
104 
105         /** @brief Columns, in the same order as UI. */
106         typedef enum
107         {
108             SCHEDCOL_NAME = 0,
109             SCHEDCOL_STATUS,
110             SCHEDCOL_CAPTURES,
111             SCHEDCOL_ALTITUDE,
112             SCHEDCOL_SCORE,
113             SCHEDCOL_STARTTIME,
114             SCHEDCOL_ENDTIME,
115             SCHEDCOL_DURATION,
116             SCHEDCOL_LEADTIME,
117             SCHEDCOL_COUNT
118         } SchedulerColumns;
119 
120         /** @brief IterationTypes, the different types of scheduler iterations that are run. */
121         typedef enum
122         {
123             RUN_WAKEUP = 0,
124             RUN_SCHEDULER,
125             RUN_JOBCHECK,
126             RUN_SHUTDOWN,
127             RUN_NOTHING
128         } SchedulerTimerState;
129 
130         /** @brief Constructor, the starndard scheduler constructor. */
131         Scheduler();
132         /** @brief DebugConstructor, a constructor used in testing with a mock ekos. */
133         Scheduler(const QString &ekosPathStr, const QString &ekosInterfaceStr);
134         ~Scheduler() = default;
135 
136         QString getCurrentJobName();
getCurrentJob()137         SchedulerJob *getCurrentJob()
138         {
139             return currentJob;
140         }
141 
142         void appendLogText(const QString &);
logText()143         QStringList logText()
144         {
145             return m_LogText;
146         }
getLogText()147         QString getLogText()
148         {
149             return m_LogText.join("\n");
150         }
151         void clearLog();
152         void applyConfig();
153 
154         void addObject(SkyObject *object);
155 
156         /**
157              * @brief startSlew DBus call for initiating slew
158              */
159         void startSlew();
160 
161         /**
162              * @brief startFocusing DBus call for feeding ekos the specified settings and initiating focus operation
163              */
164         void startFocusing();
165 
166         /**
167              * @brief startAstrometry initiation of the capture and solve operation. We change the job state
168              * after solver is started
169              */
170         void startAstrometry();
171 
172         /**
173              * @brief startGuiding After ekos is fed the calibration options, we start the guiding process
174              * @param resetCalibration By default calibration is not reset until it is explicitly requested
175              */
176         void startGuiding(bool resetCalibration = false);
177 
178         /**
179              * @brief startCapture The current job file name is solved to an url which is fed to ekos. We then start the capture process
180              * @param restart Set to true if the goal to restart an existing sequence. The only difference is that when a sequence is restarted, sequence file
181              * is not loaded from disk again since that results in erasing all the history of the capture process.
182              */
183         void startCapture(bool restart = false);
184 
185         /**
186              * @brief getNextAction Checking for the next appropriate action regarding the current state of the scheduler  and execute it
187              */
188         void getNextAction();
189 
190         /**
191              * @brief disconnectINDI disconnect all INDI devices from server.
192              */
193         void disconnectINDI();
194 
195         /**
196              * @brief stopEkos shutdown Ekos completely
197              */
198         void stopEkos();
199 
200         /**
201              * @brief stopGuiding After guiding is done we need to stop the process
202              */
203         void stopGuiding();
204 
205         /**
206              * @brief setSolverAction set the GOTO mode for the solver
207              * @param mode 0 For Sync, 1 for SlewToTarget, 2 for Nothing
208              */
209         void setSolverAction(Align::GotoMode mode);
210 
211         /** @defgroup SchedulerDBusInterface Ekos DBus Interface - Scheduler Module
212              * Ekos::Align interface provides primary functions to run and stop the scheduler.
213             */
214 
215         /*@{*/
216 
217         /** DBUS interface function.
218              * @brief Start the scheduler main loop and evaluate jobs and execute them accordingly.
219              */
220         Q_SCRIPTABLE Q_NOREPLY void start();
221 
222         /** DBUS interface function.
223              * @brief Stop the scheduler.
224              */
225         Q_SCRIPTABLE Q_NOREPLY void stop();
226 
227         /** DBUS interface function.
228              * @brief Remove all scheduler jobs
229              */
230         Q_SCRIPTABLE Q_NOREPLY void removeAllJobs();
231 
232         /** DBUS interface function.
233              * @brief Loads the Ekos Scheduler List (.esl) file.
234              * @param fileURL path to a file
235              * @return true if loading file is successful, false otherwise.
236              */
237         Q_SCRIPTABLE bool loadScheduler(const QString &fileURL);
238 
239         /** DBUS interface function.
240          * @brief Set the file URL pointing to the capture sequence file
241          * @param sequenceFileURL URL of the capture sequence file
242          */
243         Q_SCRIPTABLE void setSequence(const QString &sequenceFileURL);
244 
245         /** DBUS interface function.
246              * @brief Resets all jobs to IDLE
247              */
248         Q_SCRIPTABLE void resetAllJobs();
249 
250         /** DBUS interface function.
251              * @brief Resets all jobs to IDLE
252              */
253         Q_SCRIPTABLE void sortJobsPerAltitude();
254 
status()255         Ekos::SchedulerState status()
256         {
257             return state;
258         }
259 
setProfile(const QString & profile)260         void setProfile(const QString &profile)
261         {
262             schedulerProfileCombo->setCurrentText(profile);
263         }
profile()264         QString profile()
265         {
266             return schedulerProfileCombo->currentText();
267         }
268 
269         /**
270          * @brief retrieve the error handling strategy from the UI
271          */
272         ErrorHandlingStrategy getErrorHandlingStrategy();
273 
274         /**
275          * @brief select the error handling strategy (no restart, restart after all terminated, restart immediately)
276          */
277         void setErrorHandlingStrategy (ErrorHandlingStrategy strategy);
278 
279         /** @}*/
280 
281         // TODO: This section of static public and private methods should someday
282         // be moved from Scheduler and placed in a separate class,
283         // e.g. SchedulerPlanner or SchedulerJobEval
284 
285         /**
286              * @brief setupJob Massive initialization of a SchedulerJob for testing and exectution
287              * @param job Target
288         */
289         static void setupJob(
290             SchedulerJob &job, const QString &name, int priority, const dms &ra,
291             const dms &dec, double djd, double rotation, const QUrl &sequenceUrl, const QUrl &fitsUrl,
292             SchedulerJob::StartupCondition startup, const QDateTime &startupTime, int16_t startupOffset,
293             SchedulerJob::CompletionCondition completion, const QDateTime &completionTime, int completionRepeats,
294             double minimumAltitude, double minimumMoonSeparation, bool enforceWeather, bool enforceTwilight,
295             bool enforceArtificialHorizon, bool track, bool focus, bool align, bool guide);
296 
297         /**
298              * @brief evaluateJobs Computes estimated start and end times for the SchedulerJobs passed in. Returns a proposed schedule.
299              * @param jobs The input list of SchedulerJobs to evaluate.
300              * @param state The current scheduler state.
301              * @param capturedFramesCount which parts of the schedulerJobs have already been completed.
302              * @param dawn next dawn, as a KStarsDateTime
303              * @param dusk next dusk, as a KStarsDateTime
304              * @param rescheduleErrors whether jobs that failed with errors should be rescheduled.
305              * @param restartJobs whether jobs that failed for one reason or another shoulc be rescheduled.
306              * @param possiblyDelay a return value indicating whether the timer should try scheduling again after a delay.
307              * @param scheduler instance of the scheduler used for logging. Can be nullptr.
308              * @return Total score
309              */
310         static QList<SchedulerJob *> evaluateJobs(QList<SchedulerJob *> &jobs, SchedulerState state,
311                 const QMap<QString, uint16_t> &capturedFramesCount, QDateTime const &dawn, QDateTime const &dusk,
312                 bool rescheduleErrors, bool restartJobs, bool *possiblyDelay, Scheduler *scheduler);
313         /**
314              * @brief calculateJobScore Calculate job dark sky score, altitude score, and moon separation scores and returns the sum.
315              * @param job Target
316              * @param dawn next dawn, as a KStarsDateTime
317              * @param dusk next dusk, as a KStarsDateTime
318              * @param when date and time to evaluate constraints, now if omitted.
319              * @return Total score
320              */
321         static int16_t calculateJobScore(SchedulerJob const *job, QDateTime const &dawn, QDateTime const &dusk,
322                                          QDateTime const &when = QDateTime());
323 
324         /**
325              * @brief estimateJobTime Estimates the time the job takes to complete based on the sequence file and what modules to utilize during the observation run.
326              * @param job target job
327              * @param capturedFramesCount a map of what's been captured already
328              * @param scheduler instance of the scheduler used for logging. Can be nullptr.
329              * @return Estimated time in seconds.
330              */
331         static bool estimateJobTime(SchedulerJob *schedJob, const QMap<QString, uint16_t> &capturedFramesCount,
332                                     Scheduler *scheduler);
333         /**
334              * @brief loadSequenceQueue Loads what's necessary to estimate job completion time from a capture sequence queue file
335              * @param fileURL the filename
336              * @param schedJob the SchedulerJob is modified accoring to the contents of the sequence queue
337              * @param jobs the returned values read from the file
338              * @param hasAutoFocus a return value indicating whether autofocus can be triggered by the sequence.
339               * @param scheduler instance of the scheduler used for logging. Can be nullptr.
340              * @return Estimated time in seconds.
341              */
342 
343         static bool loadSequenceQueue(const QString &fileURL, SchedulerJob *schedJob, QList<SequenceJob *> &jobs,
344                                       bool &hasAutoFocus, Scheduler *scheduler);
345 
346         /**
347              * @brief getDarkSkyScore Get the dark sky score of a date and time. The further from dawn the better.
348              * @param when date and time to check the dark sky score, now if omitted
349              * @return Dark sky score. Daylight get bad score, as well as pre-dawn to dawn.
350              */
351         static int16_t getDarkSkyScore(QDateTime const &dawn, QDateTime const &dusk, QDateTime const &when = QDateTime());
352 
353         /** @brief Setter used in testing to fix the local time. Otherwise getter gets from KStars instance. */
354         /** @{ */
355         static KStarsDateTime getLocalTime();
setLocalTime(KStarsDateTime * time)356         static void setLocalTime(KStarsDateTime *time)
357         {
358             storedLocalTime = time;
359         }
hasLocalTime()360         static bool hasLocalTime()
361         {
362             return storedLocalTime != nullptr;
363         }
364         /** @} */
365 
366     private:
367         /**
368              * @brief processJobInfo a utility used by loadSequenceQueue() to help it read a capture sequence file
369              * @param root the filename
370              * @param schedJob the SchedulerJob is modified accoring to the contents of the sequence queue
371              * @return a capture sequence
372              */
373         static SequenceJob *processJobInfo(XMLEle *root, SchedulerJob *schedJob);
374 
375         /**
376              * @brief timeHeuristics Estimates the number of seconds of overhead above and beyond imaging time, used by estimateJobTime.
377              * @param schedJob the scheduler job.
378              * @return seconds of overhead.
379              */
380         static int timeHeuristics(const SchedulerJob *schedJob);
381 
382         // Used in testing, instead of KStars::Instance() resources
383         static KStarsDateTime *storedLocalTime;
384 
385         friend TestSchedulerUnit;
386 
387         // TODO: See above TODO. End of static methods that might be moved to
388         // a separate Scheduler-related class.
389 
390         /*@{*/
391 
392     private:
393         /** @internal Safeguard flag to avoid registering signals from widgets multiple times.
394          */
395         bool jobChangesAreWatched { false };
396 
397     protected:
398         /** @internal Enables signal watch on SchedulerJob form values in order to apply changes to current job.
399           * @param enable is the toggle flag, true to watch for changes, false to ignore them.
400           */
401         void watchJobChanges(bool enable);
402 
403         /** @internal Marks the currently selected SchedulerJob as modified change.
404          *
405          * This triggers job re-evaluation.
406          * Next time save button is invoked, the complete content is written to disk.
407           */
408         void setDirty();
409         /** @} */
410 
411     protected:
412         /** @internal Associate job table cells on a row to the corresponding SchedulerJob.
413          * @param row is an integer indexing the row to associate cells from, and also the index of the job in the job list..
414          */
415         void setJobStatusCells(int row);
416 
417     protected slots:
418 
419         /**
420          * @brief registerNewModule Register an Ekos module as it arrives via DBus
421          * and create the appropriate DBus interface to communicate with it.
422          * @param name of module
423          */
424         void registerNewModule(const QString &name);
425 
426         /**
427          * @brief syncProperties Sync startup properties from the various device to enable/disable features in the scheduler
428          * like the ability to park/unpark..etc
429          */
430         void syncProperties();
431 
432         /**
433          * @brief checkInterfaceReady Sometimes syncProperties() is not sufficient since the ready signal could have fired already
434          * and cannot be relied on to know once a module interface is ready. Therefore, we explicitly check if the module interface
435          * is ready.
436          * @param iface interface to test for readiness.
437          */
438         void checkInterfaceReady(QDBusInterface *iface);
439 
440         void setAlignStatus(Ekos::AlignState status);
441         void setGuideStatus(Ekos::GuideState status);
442         void setCaptureStatus(Ekos::CaptureState status);
443         void setFocusStatus(Ekos::FocusState status);
444         void setMountStatus(ISD::Telescope::Status status);
445         void setWeatherStatus(ISD::Weather::Status status);
446 
447         /**
448              * @brief select object from KStars's find dialog.
449              */
450         void selectObject();
451 
452         /**
453              * @brief Selects FITS file for solving.
454              */
455         void selectFITS();
456 
457         /**
458              * @brief Selects sequence queue.
459              */
460         void selectSequence();
461 
462         /**
463              * @brief Selects sequence queue.
464              */
465         void selectStartupScript();
466 
467         /**
468              * @brief Selects sequence queue.
469              */
470         void selectShutdownScript();
471 
472         /**
473              * @brief addToQueue Construct a SchedulerJob and add it to the queue or save job settings from current form values.
474              * jobUnderEdit determines whether to add or edit
475              */
476         void saveJob();
477 
478         /**
479              * @brief addJob Add a new job from form values
480              */
481         void addJob();
482 
483         /**
484              * @brief editJob Edit an observation job
485              * @param i index model in queue table
486              */
487         void loadJob(QModelIndex i);
488 
489         /**
490              * @brief removeJob Remove a job from the currently selected row. If no row is selected, it remove the last job in the queue.
491              */
492         void removeJob();
493 
494         /**
495              * @brief setJobAddApply Set first button state to add new job or apply changes.
496              */
497         void setJobAddApply(bool add_mode);
498 
499         /**
500              * @brief setJobManipulation Enable or disable job manipulation buttons.
501              */
502         void setJobManipulation(bool can_reorder, bool can_delete);
503 
504         /**
505          * @brief set all GUI fields to the values of the given scheduler job
506          */
507         void syncGUIToJob(SchedulerJob *job);
508 
509         /**
510              * @brief jobSelectionChanged Update UI state when the job list is clicked once.
511              */
512         void clickQueueTable(QModelIndex index);
513 
514         /**
515          * @brief Update scheduler parameters to the currently selected scheduler job
516          * @param current table position
517          * @param previous table position
518          */
519         void queueTableSelectionChanged(QModelIndex current, QModelIndex previous);
520 
521         /**
522              * @brief reorderJobs Change the order of jobs in the UI based on a subset of its jobs.
523              */
524         bool reorderJobs(QList<SchedulerJob*> reordered_sublist);
525 
526         /**
527              * @brief moveJobUp Move the selected job up in the job list.
528              */
529         void moveJobUp();
530 
531         /**
532             * @brief moveJobDown Move the selected job down in the list.
533             */
534         void moveJobDown();
535 
536         /**
537          * @brief shouldSchedulerSleep Check if the scheduler needs to sleep until the job is ready
538          * @param currentJob Job to check
539          * @return True if we set the scheduler to sleep mode. False, if not required and we need to execute now
540          */
541         bool shouldSchedulerSleep(SchedulerJob *currentJob);
542 
543         bool completeShutdown();
544         void toggleScheduler();
545         void pause();
546         void setPaused();
547         void save();
548         void saveAs();
549 
550         /**
551          * @brief load Open a file dialog to select an ESL file, and load its contents.
552          * @param clearQueue Clear the queue before loading, or append ESL contents to queue.
553          * @param filename If not empty, this file will be used instead of poping up a dialog.
554          */
555         void load(bool clearQueue, const QString &filename = "");
556 
557         /**
558          * @brief appendEkosScheduleList Append the contents of an ESL file to the queue.
559          * @param fileURL File URL to load contents from.
560          * @return True if contents were loaded successfully, else false.
561          */
562         bool appendEkosScheduleList(const QString &fileURL);
563 
564         void resetJobEdit();
565 
566         /**
567          * @brief updateNightTime update the Twilight restriction with the argument job properties.
568          * @param job SchedulerJob for which to display the next dawn and dusk, or the job currently selected if null, or today's next dawn and dusk if no job is selected.
569          */
570         void updateNightTime(SchedulerJob const * job = nullptr);
571 
572         /**
573              * @brief checkJobStatus Check the overall state of the scheduler, Ekos, and INDI. When all is OK, it calls evaluateJobs() when no job is current or executeJob() if a job is selected.
574              * @return False if this function needs to be called again later, true if situation is stable and operations may continue.
575              */
576         bool checkStatus();
577 
578         /**
579              * @brief checkJobStage Check the progress of the job states and make DBUS call to start the next stage until the job is complete.
580              */
581         void checkJobStage();
582 
583         /**
584              * @brief findNextJob Check if the job met the completion criteria, and if it did, then it search for next job candidate. If no jobs are found, it starts the shutdown stage.
585              */
586         void findNextJob();
587 
588         /**
589              * @brief stopCurrentJobAction Stop whatever action taking place in the current job (eg. capture, guiding...etc).
590              */
591         void stopCurrentJobAction();
592 
593         /**
594              * @brief manageConnectionLoss Mitigate loss of connection with the INDI server.
595              * @return true if connection to Ekos/INDI should be attempted again, false if not mitigation is available or needed.
596              */
597         bool manageConnectionLoss();
598 
599         /**
600              * @brief readProcessOutput read running script process output and display it in Ekos
601              */
602         void readProcessOutput();
603 
604         /**
605              * @brief checkProcessExit Check script process exist status. This is called when the process exists either normally or abnormally.
606              * @param exitCode exit code from the script process. Depending on the exist code, the status of startup/shutdown procedure is set accordingly.
607              */
608         void checkProcessExit(int exitCode);
609 
610         /**
611              * @brief resumeCheckStatus If the scheduler primary loop was suspended due to weather or sleep event, resume it again.
612              */
613         void resumeCheckStatus();
614 
615         /**
616              * @brief checkWeather Check weather status and act accordingly depending on the current status of the scheduler and running jobs.
617              */
618         //void checkWeather();
619 
620         /**
621              * @brief wakeUpScheduler Wake up scheduler from sleep state
622              */
623         void wakeUpScheduler();
624 
625         /**
626              * @brief startJobEvaluation Start job evaluation only without starting the scheduler process itself. Display the result to the user.
627              */
628         void startJobEvaluation();
629 
630         /**
631              * @brief startMosaicTool Start Mosaic tool and create jobs if necessary.
632              */
633         void startMosaicTool();
634 
635         /**
636              * @brief displayTwilightWarning Display twilight warning to user if it is unchecked.
637              */
638         void checkTwilightWarning(bool enabled);
639 
640         void runStartupProcedure();
641         void checkStartupProcedure();
642 
643         void runShutdownProcedure();
644         void checkShutdownProcedure();
645 
646         void setINDICommunicationStatus(Ekos::CommunicationStatus status);
647         void setEkosCommunicationStatus(Ekos::CommunicationStatus status);
648 
649         void simClockScaleChanged(float);
650         void simClockTimeChanged();
651 
652     signals:
653         void newLog(const QString &text);
654         void newStatus(Ekos::SchedulerState state);
655         void weatherChanged(ISD::Weather::Status state);
656         void newTarget(const QString &);
657 
658     private:
659         /**
660              * @brief evaluateJobs evaluates the current state of each objects and gives each one a score based on the constraints.
661              * Given that score, the scheduler will decide which is the best job that needs to be executed.
662              */
663         void evaluateJobs(bool evaluateOnly);
664         void processJobs(QList<SchedulerJob *> sortedJobs, bool jobEvaluationOnly);
665 
666         /**
667              * @brief executeJob After the best job is selected, we call this in order to start the process that will execute the job.
668              * checkJobStatus slot will be connected in order to figure the exact state of the current job each second
669              * @param value
670              */
671         void executeJob(SchedulerJob *job);
672 
673         void executeScript(const QString &filename);
674 
675         /**
676              * @brief getWeatherScore Get current weather condition score.
677              * @return If weather condition OK, return score 0, else bad score.
678              */
679         int16_t getWeatherScore() const;
680 
681         /**
682              * @brief calculateDawnDusk Get dawn and dusk times for today
683              */
684         static void calculateDawnDusk();
685 
686         /**
687              * @brief checkEkosState Check ekos startup stages and take whatever action necessary to get Ekos up and running
688              * @return True if Ekos is running, false if Ekos start up is in progress.
689              */
690         bool checkEkosState();
691 
692         /**
693              * @brief isINDIConnected Determines the status of the INDI connection.
694              * @return True if INDI connection is up and usable, else false.
695              */
696         bool isINDIConnected();
697 
698         /**
699              * @brief checkINDIState Check INDI startup stages and take whatever action necessary to get INDI devices connected.
700              * @return True if INDI devices are connected, false if it is under progress.
701              */
702         bool checkINDIState();
703 
704         /**
705              * @brief checkStartupState Check startup procedure stages and make sure all stages are complete.
706              * @return True if startup is complete, false otherwise.
707              */
708         bool checkStartupState();
709 
710         /**
711              * @brief checkShutdownState Check shutdown procedure stages and make sure all stages are complete.
712              * @return
713              */
714         bool checkShutdownState();
715 
716         /**
717              * @brief checkParkWaitState Check park wait state.
718              * @return If parking/unparking in progress, return false. If parking/unparking complete, return true.
719              */
720         bool checkParkWaitState();
721 
722         /**
723              * @brief parkMount Park mount
724              */
725         void parkMount();
726 
727         /**
728              * @brief unParkMount Unpark mount
729              */
730         void unParkMount();
731 
732         /**
733              * @return True if mount is parked
734              */
735         bool isMountParked();
736 
737         /**
738              * @brief parkDome Park dome
739              */
740         void parkDome();
741 
742         /**
743              * @brief unParkDome Unpark dome
744              */
745         void unParkDome();
746 
747         /**
748              * @return True if dome is parked
749              */
750         bool isDomeParked();
751 
752         /**
753              * @brief parkCap Close dust cover
754              */
755         void parkCap();
756 
757         /**
758              * @brief unCap Open dust cover
759              */
760         void unParkCap();
761 
762         /**
763              * @brief checkMountParkingStatus check mount parking status and updating corresponding states accordingly.
764              */
765         void checkMountParkingStatus();
766 
767         /**
768              * @brief checkDomeParkingStatus check dome parking status and updating corresponding states accordingly.
769              */
770         void checkDomeParkingStatus();
771 
772         /**
773              * @brief checkDomeParkingStatus check dome parking status and updating corresponding states accordingly.
774              */
775         void checkCapParkingStatus();
776 
777         /**
778              * @brief saveScheduler Save scheduler jobs to a file
779              * @param path path of a file
780              * @return true on success, false on failure.
781              */
782         bool saveScheduler(const QUrl &fileURL);
783 
784         /**
785              * @brief processJobInfo Process the job information from a scheduler file and populate jobs accordingly
786              * @param root XML root element of JOB
787              * @return true on success, false on failure.
788              */
789         bool processJobInfo(XMLEle *root);
790 
791         /**
792              * @brief createJobSequence Creates a job sequence for the mosaic tool given the prefix and output dir. The currently selected sequence file is modified
793              * and a new version given the supplied parameters are saved to the output directory
794              * @param prefix Prefix to set for the job sequence
795              * @param outputDir Output dir to set for the job sequence
796              * @return True if new file is saved, false otherwise
797              */
798         bool createJobSequence(XMLEle *root, const QString &prefix, const QString &outputDir);
799 
800         /** @internal Change the current job, updating associated widgets.
801          * @param job is an existing SchedulerJob to set as current, or nullptr.
802          */
803         void setCurrentJob(SchedulerJob *job);
804 
805         /**
806          * @brief processFITSSelection When a FITS file is selected, open it and try to guess
807          * the object name, and its J2000 RA/DE to fill the UI with such info automatically.
808          */
809         void processFITSSelection();
810 
811         void loadProfiles();
812 
813         XMLEle *getSequenceJobRoot();
814 
815         /**
816             * @brief updateCompletedJobsCount For each scheduler job, examine sequence job storage and count captures.
817             * @param forced forces recounting captures unconditionally if true, else only IDLE, EVALUATION or new jobs are examined.
818             */
819         void updateCompletedJobsCount(bool forced = false);
820 
821         int getCompletedFiles(const QString &path, const QString &seqPrefix);
822 
823         // retrieve the guiding status
824         GuideState getGuidingStatus();
825 
826         // Returns milliseconds since startCurrentOperationTImer() was called.
827         qint64 getCurrentOperationMsec();
828         // Starts the above operation timer.
829         void startCurrentOperationTimer();
830 
831         // Controls for the guiding timer, which restarts guiding after failure.
832         void cancelGuidingTimer();
833         bool isGuidingTimerActive();
834         void startGuidingTimer(int milliseconds);
835         void processGuidingTimer();
836 
837         Ekos::Scheduler *ui { nullptr };
838         //DBus interfaces
839         QPointer<QDBusInterface> focusInterface { nullptr };
840         QPointer<QDBusInterface> ekosInterface { nullptr };
841         QPointer<QDBusInterface> captureInterface { nullptr };
842         QPointer<QDBusInterface> mountInterface { nullptr };
843         QPointer<QDBusInterface> alignInterface { nullptr };
844         QPointer<QDBusInterface> guideInterface { nullptr };
845         QPointer<QDBusInterface> domeInterface { nullptr };
846         QPointer<QDBusInterface> weatherInterface { nullptr };
847         QPointer<QDBusInterface> capInterface { nullptr };
848 
849         // Interface strings for the dbus. Changeable for mocks when testing. Private so only tests can change.
850         QString focusInterfaceString { "org.kde.kstars.Ekos.Focus" };
setFocusInterfaceString(const QString & interface)851         void setFocusInterfaceString(const QString &interface)
852         {
853             focusInterfaceString = interface;
854         }
855         QString focusPathString { "/KStars/Ekos/Focus" };
setFocusPathString(const QString & interface)856         void setFocusPathString(const QString &interface)
857         {
858             focusPathString = interface;
859         }
860 
861         // This is only used in the constructor
862         QString ekosInterfaceString { "org.kde.kstars.Ekos" };
863         QString ekosPathString { "/KStars/Ekos" };
864 
865         QString mountInterfaceString { "org.kde.kstars.Ekos.Mount" };
setMountInterfaceString(const QString & interface)866         void setMountInterfaceString(const QString &interface)
867         {
868             mountInterfaceString = interface;
869         }
870         QString mountPathString { "/KStars/Ekos/Mount" };
setMountPathString(const QString & interface)871         void setMountPathString(const QString &interface)
872         {
873             mountPathString = interface;
874         }
875 
876         QString captureInterfaceString { "org.kde.kstars.Ekos.Capture" };
setCaptureInterfaceString(const QString & interface)877         void setCaptureInterfaceString(const QString &interface)
878         {
879             captureInterfaceString = interface;
880         }
881         QString capturePathString { "/KStars/Ekos/Capture" };
setCapturePathString(const QString & interface)882         void setCapturePathString(const QString &interface)
883         {
884             capturePathString = interface;
885         }
886 
887         QString alignInterfaceString { "org.kde.kstars.Ekos.Align" };
setAlignInterfaceString(const QString & interface)888         void setAlignInterfaceString(const QString &interface)
889         {
890             alignInterfaceString = interface;
891         }
892         QString alignPathString { "/KStars/Ekos/Align" };
setAlignPathString(const QString & interface)893         void setAlignPathString(const QString &interface)
894         {
895             alignPathString = interface;
896         }
897 
898         QString guideInterfaceString { "org.kde.kstars.Ekos.Guide" };
setGuideInterfaceString(const QString & interface)899         void setGuideInterfaceString(const QString &interface)
900         {
901             guideInterfaceString = interface;
902         }
903         QString guidePathString { "/KStars/Ekos/Guide" };
setGuidePathString(const QString & interface)904         void setGuidePathString(const QString &interface)
905         {
906             guidePathString = interface;
907         }
908 
909         QString domeInterfaceString { "org.kde.kstars.Ekos.Dome" };
setDomeInterfaceString(const QString & interface)910         void setDomeInterfaceString(const QString &interface)
911         {
912             domeInterfaceString = interface;
913         }
914         QString domePathString { "/KStars/Ekos/Dome" };
setDomePathString(const QString & interface)915         void setDomePathString(const QString &interface)
916         {
917             domePathString = interface;
918         }
919 
920         QString weatherInterfaceString { "org.kde.kstars.Ekos.Weather" };
setWeatherInterfaceString(const QString & interface)921         void setWeatherInterfaceString(const QString &interface)
922         {
923             weatherInterfaceString = interface;
924         }
925         QString weatherPathString { "/KStars/Ekos/Weather" };
setWeatherPathString(const QString & interface)926         void setWeatherPathString(const QString &interface)
927         {
928             weatherPathString = interface;
929         }
930 
931         QString dustCapInterfaceString { "org.kde.kstars.Ekos.DustCap" };
setDustCapInterfaceString(const QString & interface)932         void setDustCapInterfaceString(const QString &interface)
933         {
934             dustCapInterfaceString = interface;
935         }
936         QString dustCapPathString { "/KStars/Ekos/DustCap" };
setDustCapPathString(const QString & interface)937         void setDustCapPathString(const QString &interface)
938         {
939             dustCapPathString = interface;
940         }
941 
942         // Scheduler and job state and stages
943         SchedulerState state { SCHEDULER_IDLE };
944         EkosState ekosState { EKOS_IDLE };
945         INDIState indiState { INDI_IDLE };
946         StartupState startupState { STARTUP_IDLE };
947         ShutdownState shutdownState { SHUTDOWN_IDLE };
948         ParkWaitStatus parkWaitState { PARKWAIT_IDLE };
949         Ekos::CommunicationStatus m_EkosCommunicationStatus { Ekos::Idle };
950         Ekos::CommunicationStatus m_INDICommunicationStatus { Ekos::Idle };
951         /// List of all jobs as entered by the user or file
952         QList<SchedulerJob *> jobs;
953         /// Active job
954         SchedulerJob *currentJob { nullptr };
955         /// URL to store the scheduler file
956         QUrl schedulerURL;
957         /// URL for Ekos Sequence
958         QUrl sequenceURL;
959         /// FITS URL to solve
960         QUrl fitsURL;
961         /// Startup script URL
962         QUrl startupScriptURL;
963         /// Shutdown script URL
964         QUrl shutdownScriptURL;
965         /// Store all log strings
966         QStringList m_LogText;
967         /// Busy indicator widget
968         QProgressIndicator *pi { nullptr };
969         /// Are we editing a job right now? Job row index
970         int jobUnderEdit { -1 };
971         /// Pointer to Geographic location
972         GeoLocation *geo { nullptr };
973         /// How many repeated job batches did we complete thus far?
974         uint16_t captureBatch { 0 };
975         /// Startup and Shutdown scripts process
976         QProcess scriptProcess;
977         /// Store next dawn to calculate dark skies range
978         static QDateTime Dawn;
979         /// Store next dusk to calculate dark skies range
980         static QDateTime Dusk;
981         /// Pre-dawn is where we stop all jobs, it is a user-configurable value before Dawn.
982         static QDateTime preDawnDateTime;
983         /// Was job modified and needs saving?
984         bool mDirty { false };
985         /// Keep watch of weather status
986         ISD::Weather::Status weatherStatus { ISD::Weather::WEATHER_IDLE };
987         /// Keep track of how many times we didn't receive weather updates
988         uint8_t noWeatherCounter { 0 };
989 
990         // Utilities to control the preemptiveShutdown feature.
991         // Is the scheduler shutting down until later when it will resume a job?
992         void enablePreemptiveShutdown(const QDateTime &wakeupTime);
993         void disablePreemptiveShutdown();
994         QDateTime getPreemptiveShutdownWakeupTime();
995         bool preemptiveShutdown();
996         // The various preemptiveShutdown states are controlled by this one variable.
997         QDateTime preemptiveShutdownWakeupTime;
998 
999         /// Keep track of Load & Slew operation
1000         bool loadAndSlewProgress { false };
1001         /// Check if initial autofocus is completed and do not run autofocus until there is a change is telescope position/alignment.
1002         bool autofocusCompleted { false };
1003         /// Keep track of INDI connection failures
1004         uint8_t indiConnectFailureCount { 0 };
1005         /// Keep track of Ekos connection failures
1006         uint8_t ekosConnectFailureCount { 0 };
1007         /// Keep track of Ekos focus module failures
1008         uint8_t focusFailureCount { 0 };
1009         /// Keep track of Ekos guide module failures
1010         uint8_t guideFailureCount { 0 };
1011         /// Keep track of Ekos align module failures
1012         uint8_t alignFailureCount { 0 };
1013         /// Keep track of Ekos capture module failures
1014         uint8_t captureFailureCount { 0 };
1015         /// Counter to keep debug logging in check
1016         uint8_t checkJobStageCounter { 0 };
1017         /// Call checkWeather when weatherTimer time expires. It is equal to the UpdatePeriod time in INDI::Weather device.
1018         //QTimer weatherTimer;
1019 
1020         /// Delay for restarting the guider
1021         /// used by cancelGuidingTimer(), isGuidingTimerActive(), startGuidingTimer
1022         /// and processGuidingTimer.
1023         int restartGuidingInterval { -1 };
1024         KStarsDateTime restartGuidingTime;
1025 
1026         /// Generic time to track timeout of current operation in progress.
1027         /// Used by startCurrentOperationTimer() and getCurrentOperationMsec().
1028         KStarsDateTime currentOperationTime;
1029         bool currentOperationTimeStarted { false };
1030 
1031         QUrl dirPath;
1032 
1033         QMap<QString, uint16_t> m_CapturedFramesCount;
1034 
1035         bool m_MountReady { false };
1036         bool m_CaptureReady { false };
1037         bool m_DomeReady { false };
1038         bool m_CapReady { false };
1039 
1040         // When a module is commanded to perform an action, wait this many milliseconds
1041         // before check its state again. If State is still IDLE, then it either didn't received the command
1042         // or there is another problem.
1043         static const uint32_t ALIGN_INACTIVITY_TIMEOUT      = 120000;
1044         static const uint32_t FOCUS_INACTIVITY_TIMEOUT      = 120000;
1045         static const uint32_t CAPTURE_INACTIVITY_TIMEOUT    = 120000;
1046         static const uint16_t GUIDE_INACTIVITY_TIMEOUT      = 60000;
1047 
1048         // Methods & variables that control the scheduler's iterations.
1049 
1050         // Initializes the scheduler, then calls iterate().
1051         void run();
1052         // Repeatedly runs a scheduler iteration and then sleeps timerInterval millisconds
1053         // and run the next iteration. This continues until the sleep time is negative.
1054         void iterate();
1055         // Initialize the scheduler.
1056         void init();
1057         // Run a single scheduler iteration.
1058         int runSchedulerIteration();
1059 
1060         // This is the time between typical scheduler iterations.
1061         // The time can be modified for testing.
1062         int m_UpdatePeriodMs = 1000;
setUpdateInterval(int ms)1063         void setUpdateInterval(int ms)
1064         {
1065             m_UpdatePeriodMs = ms;
1066         }
1067         // Setup the parameters for the next scheduler iteration.
1068         // When milliseconds is not passed in, it uses m_UpdatePeriodMs.
1069         void setupNextIteration(SchedulerTimerState nextState);
1070         void setupNextIteration(SchedulerTimerState nextState, int milliseconds);
1071         // True if the scheduler is between iterations and delaying longer than the typical update period.
1072         bool currentlySleeping();
1073         // Used by the constructor in testing mainly so a mock ekos could be used.
1074         void setupScheduler(const QString &ekosPathStr, const QString &ekosInterfaceStr);
1075         // Prints all the relative state variables set during an iteration. For debugging.
1076         void printStates(const QString &label);
1077 
1078 
1079         // The type of scheduler iteration that should be run next.
1080         SchedulerTimerState timerState { RUN_NOTHING };
1081         // Variable keeping the number of millisconds the scheduler should wait
1082         // after the current scheduler iteration.
1083         int timerInterval { -1 };
1084         // Whether the scheduler has been setup for the next iteration,
1085         // that is, whether timerInterval and timerState have been set this iteration.
1086         bool iterationSetup { false };
1087         // The timer used to wakeup the scheduler between iterations.
1088         QTimer iterationTimer;
1089         // Counter for how many scheduler iterations have been processed.
1090         int schedulerIteration { 0 };
1091         // The time when the scheduler first started running iterations.
1092         qint64 startMSecs { 0 };
1093 
1094         friend TestEkosSchedulerOps;
1095 };
1096 }
1097