1 /*  Ekos Scheduler Job
2     SPDX-FileCopyrightText: Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #pragma once
8 
9 #include "skypoint.h"
10 
11 #include <QUrl>
12 #include <QMap>
13 #include "ksmoon.h"
14 #include "kstarsdatetime.h"
15 
16 class ArtificialHorizon;
17 class QTableWidgetItem;
18 class QLabel;
19 class KSMoon;
20 class TestSchedulerUnit;
21 class TestEkosSchedulerOps;
22 class dms;
23 
24 class SchedulerJob
25 {
26     public:
27         SchedulerJob();
28 
29         /** @brief States of a SchedulerJob. */
30         typedef enum
31         {
32             JOB_IDLE,       /**< Job was just created, and is not evaluated yet */
33             JOB_EVALUATION, /**< Job is being evaluated */
34             JOB_SCHEDULED,  /**< Job was evaluated, and has a schedule */
35             JOB_BUSY,       /**< Job is being processed */
36             JOB_ERROR,      /**< Job encountered a fatal issue while processing, and must be reset manually */
37             JOB_ABORTED,    /**< Job encountered a transitory issue while processing, and will be rescheduled */
38             JOB_INVALID,    /**< Job has an incorrect configuration, and cannot proceed */
39             JOB_COMPLETE    /**< Job finished all required captures */
40         } JOBStatus;
41 
42         /** @brief Running stages of a SchedulerJob. */
43         typedef enum
44         {
45             STAGE_IDLE,
46             STAGE_SLEWING,
47             STAGE_SLEW_COMPLETE,
48             STAGE_FOCUSING,
49             STAGE_FOCUS_COMPLETE,
50             STAGE_ALIGNING,
51             STAGE_ALIGN_COMPLETE,
52             STAGE_RESLEWING,
53             STAGE_RESLEWING_COMPLETE,
54             STAGE_POSTALIGN_FOCUSING,
55             STAGE_POSTALIGN_FOCUSING_COMPLETE,
56             STAGE_GUIDING,
57             STAGE_GUIDING_COMPLETE,
58             STAGE_CAPTURING,
59             STAGE_COMPLETE
60         } JOBStage;
61 
62         /** @brief Conditions under which a SchedulerJob may start. */
63         typedef enum
64         {
65             START_ASAP,
66             START_CULMINATION,
67             START_AT
68         } StartupCondition;
69 
70         /** @brief Conditions under which a SchedulerJob may complete. */
71         typedef enum
72         {
73             FINISH_SEQUENCE,
74             FINISH_REPEAT,
75             FINISH_LOOP,
76             FINISH_AT
77         } CompletionCondition;
78 
79         /** @brief Actions that may be processed when running a SchedulerJob.
80          * FIXME: StepPipeLine is actually a mask, change this into a bitfield.
81          */
82         typedef enum
83         {
84             USE_NONE  = 0,
85             USE_TRACK = 1 << 0,
86             USE_FOCUS = 1 << 1,
87             USE_ALIGN = 1 << 2,
88             USE_GUIDE = 1 << 3
89         } StepPipeline;
90 
91         /** @brief Coordinates of the target of this job. */
92         /** @{ */
getTargetCoords()93         SkyPoint const &getTargetCoords() const
94         {
95             return targetCoords;
96         }
97         void setTargetCoords(const dms &ra, const dms &dec, double djd);
98         /** @} */
99 
getRotation()100         double getRotation()
101         {
102             return rotation;
103         }
104         void setRotation(double rotation);
105 
106         /** @brief Capture sequence this job uses while running. */
107         /** @{ */
getSequenceFile()108         QUrl getSequenceFile() const
109         {
110             return sequenceFile;
111         }
112         void setSequenceFile(const QUrl &value);
113         /** @} */
114 
115         /** @brief FITS file whose plate solve produces target coordinates. */
116         /** @{ */
getFITSFile()117         QUrl getFITSFile() const
118         {
119             return fitsFile;
120         }
121         void setFITSFile(const QUrl &value);
122         /** @} */
123 
124         /** @brief Minimal target altitude to process this job */
125         /** @{ */
getMinAltitude()126         double getMinAltitude() const
127         {
128             return minAltitude;
129         }
130         void setMinAltitude(const double &value);
131         /** @} */
132 
133         /** @brief Does this job have a min-altitude parameter. */
134         /** @{ */
hasMinAltitude()135         bool hasMinAltitude() const
136         {
137             return UNDEFINED_ALTITUDE < minAltitude;
138         }
139         static constexpr int UNDEFINED_ALTITUDE = -90;
140         /** @} */
141 
142         /** @brief Does this job have any altitude constraints. */
143         /** @{ */
144         bool hasAltitudeConstraint() const;
145         /** @} */
146 
147         /** @brief Minimal Moon separation to process this job. */
148         /** @{ */
getMinMoonSeparation()149         double getMinMoonSeparation() const
150         {
151             return minMoonSeparation;
152         }
153         void setMinMoonSeparation(const double &value);
154         /** @} */
155 
156         /** @brief Whether to restrict this job to good weather. */
157         /** @{ */
getEnforceWeather()158         bool getEnforceWeather() const
159         {
160             return enforceWeather;
161         }
162         void setEnforceWeather(bool value);
163         /** @} */
164 
165         /** @brief Mask of actions to process for this job. */
166         /** @{ */
getStepPipeline()167         StepPipeline getStepPipeline() const
168         {
169             return stepPipeline;
170         }
171         void setStepPipeline(const StepPipeline &value);
172         /** @} */
173 
174         /** @brief Condition under which this job starts. */
175         /** @{ */
getStartupCondition()176         StartupCondition getStartupCondition() const
177         {
178             return startupCondition;
179         }
180         void setStartupCondition(const StartupCondition &value);
181         /** @} */
182 
183         /** @brief Condition under which this job completes. */
184         /** @{ */
getCompletionCondition()185         CompletionCondition getCompletionCondition() const
186         {
187             return completionCondition;
188         }
189         void setCompletionCondition(const CompletionCondition &value);
190         /** @} */
191 
192         /** @brief Target culmination proximity under which this job starts. */
193         /** @{ */
getCulminationOffset()194         int16_t getCulminationOffset() const
195         {
196             return culminationOffset;
197         }
198         void setCulminationOffset(const int16_t &value);
199         /** @} */
200 
201         /** @brief Timestamp format to use when displaying information about this job. */
202         /** @{ */
getDateTimeDisplayFormat()203         QString const &getDateTimeDisplayFormat() const
204         {
205             return dateTimeDisplayFormat;
206         }
207         void setDateTimeDisplayFormat(const QString &value);
208         /** @} */
209 
210         /** @brief Original startup condition, as entered by the user. */
211         /** @{ */
getFileStartupCondition()212         StartupCondition getFileStartupCondition() const
213         {
214             return fileStartupCondition;
215         }
216         void setFileStartupCondition(const StartupCondition &value);
217         /** @} */
218 
219         /** @brief Original time at which the job must start, as entered by the user. */
220         /** @{ */
getFileStartupTime()221         QDateTime getFileStartupTime() const
222         {
223             return fileStartupTime;
224         }
225         void setFileStartupTime(const QDateTime &value);
226         /** @} */
227 
228         /** @brief Whether this job requires re-focus while running its capture sequence. */
229         /** @{ */
getInSequenceFocus()230         bool getInSequenceFocus() const
231         {
232             return inSequenceFocus;
233         }
234         void setInSequenceFocus(bool value);
235         /** @} */
236 
237         /** @brief Job priority, low priority value means most prioritary. */
238         /** @{ */
getPriority()239         uint8_t getPriority() const
240         {
241             return priority;
242         }
243         void setPriority(const uint8_t &value);
244         /** @} */
245 
246         /** @brief Whether to restrict job to night time. */
247         /** @{ */
getEnforceTwilight()248         bool getEnforceTwilight() const
249         {
250             return enforceTwilight;
251         }
252         void setEnforceTwilight(bool value);
253         /** @} */
254 
255         /** @brief Whether to restrict job to night time. */
256         /** @{ */
getEnforceArtificialHorizon()257         bool getEnforceArtificialHorizon() const
258         {
259             return enforceArtificialHorizon;
260         }
261         void setEnforceArtificialHorizon(bool value);
262         /** @} */
263 
264         /** @brief Current name of the scheduler job. */
265         /** @{ */
getName()266         QString getName() const
267         {
268             return name;
269         }
270         void setName(const QString &value);
271         /** @} */
272 
273         /** @brief Shortcut to widget cell for job name in the job queue table. */
274         /** @{ */
getNameCell()275         QTableWidgetItem *getNameCell() const
276         {
277             return nameCell;
278         }
279         void setNameCell(QTableWidgetItem *cell);
280         /** @} */
281 
282         /** @brief Current state of the scheduler job.
283          * Setting state to JOB_ABORTED automatically resets the startup characteristics.
284          * Setting state to JOB_INVALID automatically resets the startup characteristics and the duration estimation.
285          * @see SchedulerJob::setStartupCondition, SchedulerJob::setFileStartupCondition, SchedulerJob::setStartupTime
286          * and SchedulerJob::setFileStartupTime.
287          */
288         /** @{ */
getState()289         JOBStatus getState() const
290         {
291             return state;
292         }
293         void setState(const JOBStatus &value);
294         /** @} */
295 
296         /** @brief Shortcut to widget cell for job state in the job queue table. */
297         /** @{ */
getStatusCell()298         QTableWidgetItem *getStatusCell() const
299         {
300             return statusCell;
301         }
302         void setStatusCell(QTableWidgetItem *cell);
303         /** @} */
304 
305         /** @brief Current stage of the scheduler job. */
306         /** @{ */
getStage()307         JOBStage getStage() const
308         {
309             return stage;
310         }
311         void setStage(const JOBStage &value);
312         /** @} */
313 
314         /** @brief Shortcut to widget cell for job stage in the job queue table. */
315         /** @{ */
getStageCell()316         QTableWidgetItem *getStageCell() const
317         {
318             return stageCell;
319         }
320         void setStageCell(QTableWidgetItem *cell);
getStageLabel()321         QLabel *getStageLabel() const
322         {
323             return stageLabel;
324         }
325         void setStageLabel(QLabel *label);
326         /** @} */
327 
328         /** @brief Number of captures required in the associated sequence. */
329         /** @{ */
getSequenceCount()330         int getSequenceCount() const
331         {
332             return sequenceCount;
333         }
334         void setSequenceCount(const int count);
335         /** @} */
336 
337         /** @brief Number of captures completed in the associated sequence. */
338         /** @{ */
getCompletedCount()339         int getCompletedCount() const
340         {
341             return completedCount;
342         }
343         void setCompletedCount(const int count);
344         /** @} */
345 
346         /** @brief Shortcut to widget cell for captures in the job queue table. */
347         /** @{ */
getCaptureCountCell()348         QTableWidgetItem *getCaptureCountCell() const
349         {
350             return captureCountCell;
351         }
352         void setCaptureCountCell(QTableWidgetItem *value);
353         /** @} */
354 
355         /** @brief Time at which the job must start. */
356         /** @{ */
getStartupTime()357         QDateTime getStartupTime() const
358         {
359             return startupTime;
360         }
361         void setStartupTime(const QDateTime &value);
362         /** @} */
363 
364         /** @brief Shortcut to widget cell for startup time in the job queue table. */
365         /** @{ */
getStartupCell()366         QTableWidgetItem *getStartupCell() const
367         {
368             return startupCell;
369         }
370         void setStartupCell(QTableWidgetItem *value);
371         /** @} */
372 
373         /** @brief Shortcut to widget cell for altitude in the job queue table. */
374         /** @{ */
getAltitudeCell()375         QTableWidgetItem *getAltitudeCell() const
376         {
377             return altitudeCell;
378         }
379         void setAltitudeCell(QTableWidgetItem *value);
380         /** @} */
381 
382         /** @brief Time after which the job is considered complete. */
383         /** @{ */
getCompletionTime()384         QDateTime getCompletionTime() const
385         {
386             return completionTime;
387         }
388         void setCompletionTime(const QDateTime &value);
389         /** @} */
390 
391         /** @brief Shortcut to widget cell for completion time in the job queue table. */
392         /** @{ */
getCompletionCell()393         QTableWidgetItem *getCompletionCell() const
394         {
395             return completionCell;
396         }
397         void setCompletionCell(QTableWidgetItem *value);
398         /** @} */
399 
400         /** @brief Estimation of the time the job will take to process. */
401         /** @{ */
getEstimatedTime()402         int64_t getEstimatedTime() const
403         {
404             return estimatedTime;
405         }
406         void setEstimatedTime(const int64_t &value);
407         /** @} */
408 
409         /** @brief Shortcut to widget cell for estimated time in the job queue table. */
410         /** @{ */
getEstimatedTimeCell()411         QTableWidgetItem *getEstimatedTimeCell() const
412         {
413             return estimatedTimeCell;
414         }
415         void setEstimatedTimeCell(QTableWidgetItem *value);
416         /** @} */
417 
418         /** @brief Estimation of the lead time the job will have to process. */
419         /** @{ */
getLeadTime()420         int64_t getLeadTime() const
421         {
422             return leadTime;
423         }
424         void setLeadTime(const int64_t &value);
425         /** @} */
426 
427         /** @brief Shortcut to widget cell for estimated time in the job queue table. */
428         /** @{ */
getLeadTimeCell()429         QTableWidgetItem *getLeadTimeCell() const
430         {
431             return leadTimeCell;
432         }
433         void setLeadTimeCell(QTableWidgetItem *value);
434         /** @} */
435 
436         /** @brief Current score of the scheduler job. */
437         /** @{ */
getScore()438         int getScore() const
439         {
440             return score;
441         }
442         void setScore(int value);
443         /** @} */
444 
445         /** @brief Shortcut to widget cell for job score in the job queue table. */
446         /** @{ */
getScoreCell()447         QTableWidgetItem *getScoreCell() const
448         {
449             return scoreCell;
450         }
451         void setScoreCell(QTableWidgetItem *value);
452         /** @} */
453 
454         /** @brief Whether this job requires light frames, or only calibration frames. */
455         /** @{ */
getLightFramesRequired()456         bool getLightFramesRequired() const
457         {
458             return lightFramesRequired;
459         }
460         void setLightFramesRequired(bool value);
461         /** @} */
462 
463         /** @brief Number of times this job must be repeated (in terms of capture count). */
464         /** @{ */
getRepeatsRequired()465         uint16_t getRepeatsRequired() const
466         {
467             return repeatsRequired;
468         }
469         void setRepeatsRequired(const uint16_t &value);
470         /** @} */
471 
472         /** @brief Number of times this job still has to be repeated (in terms of capture count). */
473         /** @{ */
getRepeatsRemaining()474         uint16_t getRepeatsRemaining() const
475         {
476             return repeatsRemaining;
477         }
478         void setRepeatsRemaining(const uint16_t &value);
479         /** @} */
480 
481         /** @brief The map of capture counts for this job, keyed by its capture storage signatures. */
482         /** @{ */
483         typedef QMap<QString, uint16_t> CapturedFramesMap;
getCapturedFramesMap()484         const CapturedFramesMap &getCapturedFramesMap() const
485         {
486             return capturedFramesMap;
487         }
488         void setCapturedFramesMap(const CapturedFramesMap &value);
489         /** @} */
490 
491         /** @brief Refresh all cells connected to this SchedulerJob. */
492         void updateJobCells();
493 
494         /** @brief Resetting a job to original values:
495          * - idle state and stage
496          * - original startup, none if asap, else user original setting
497          * - duration not estimated
498          * - full repeat count
499          */
500         void reset();
501 
502         /** @brief Determining whether a SchedulerJob is a duplicate of another.
503          * @param a_job is the other SchedulerJob to test duplication against.
504          * @return True if objects are different, but name and sequence file are identical, else false.
505          * @warning This is a weak comparison, but that's what the scheduler looks at to decide completion.
506          */
isDuplicateOf(SchedulerJob const * a_job)507         bool isDuplicateOf(SchedulerJob const *a_job) const
508         {
509             return this != a_job && name == a_job->name && sequenceFile == a_job->sequenceFile;
510         }
511 
512         /** @brief Compare ::SchedulerJob instances based on score.
513          * @todo This is a qSort predicate, deprecated in QT5.
514          * @arg a, b are ::SchedulerJob instances to compare.
515          * @return true if the score of b is lower than the score of a.
516          * @return false if the score of b is higher than or equal to the score of a.
517          */
518         static bool decreasingScoreOrder(SchedulerJob const *a, SchedulerJob const *b);
519 
520         /** @brief Compare ::SchedulerJob instances based on priority.
521          * @todo This is a qSort predicate, deprecated in QT5.
522          * @arg a, b are ::SchedulerJob instances to compare.
523          * @return true if the priority of a is lower than the priority of b.
524          * @return false if the priority of a is higher than or equal to the priority of b.
525          */
526         static bool increasingPriorityOrder(SchedulerJob const *a, SchedulerJob const *b);
527 
528         /** @brief Compare ::SchedulerJob instances based on altitude and movement in sky at startup time.
529          * @todo This is a qSort predicate, deprecated in QT5.
530          * @arg a, b are ::SchedulerJob instances to compare.
531          * @arg when is the date/time to use to calculate the altitude to sort with, defaulting to a's startup time.
532          * @note To obtain proper sort between several SchedulerJobs, all should have the same startup time.
533          * @note Use std:bind to bind a specific date/time to this predicate for altitude calculation.
534          * @return true is a is setting but not b.
535          * @return false if b is setting but not a.
536          * @return true otherwise, if the altitude of b is lower than the altitude of a.
537          * @return false otherwise, if the altitude of b is higher than or equal to the altitude of a.
538          */
539         static bool decreasingAltitudeOrder(SchedulerJob const *a, SchedulerJob const *b, QDateTime const &when = QDateTime());
540 
541         /** @brief Compare ::SchedulerJob instances based on startup time.
542          * @todo This is a qSort predicate, deprecated in QT5.
543          * @arg a, b are ::SchedulerJob instances to compare.
544          * @return true if the startup time of a is sooner than the priority of b.
545          * @return false if the startup time of a is later than or equal to the priority of b.
546          */
547         static bool increasingStartupTimeOrder(SchedulerJob const *a, SchedulerJob const *b);
548 
549         /**
550              * @brief getAltitudeScore Get the altitude score of an object. The higher the better
551              * @param when date and time to check the target altitude, now if omitted.
552              * @param altPtr returns the altitude in degrees if not a nullptr.
553              * @return Altitude score. Target altitude below minimum altitude required by job or setting target under 3 degrees below minimum altitude get bad score.
554              */
555         int16_t getAltitudeScore(QDateTime const &when = QDateTime(), double *altPtr = nullptr) const;
556 
557         /**
558              * @brief getMoonSeparationScore Get moon separation score. The further apart, the better, up a maximum score of 20.
559              * @param when date and time to check the moon separation, now if omitted.
560              * @return Moon separation score
561              */
562         int16_t getMoonSeparationScore(QDateTime const &when = QDateTime()) const;
563 
564         /**
565              * @brief getCurrentMoonSeparation Get current moon separation in degrees at current time for the given job
566              * @param job scheduler job
567              * @return Separation in degrees
568              */
569         double getCurrentMoonSeparation() const;
570 
571         /**
572              * @brief calculateAltitudeTime calculate the altitude time given the minimum altitude given.
573              * @param when date and time to start searching from, now if omitted.
574              * @return The date and time the target is at or above the argument altitude, valid if found, invalid if not achievable (always under altitude).
575              */
576         QDateTime calculateAltitudeTime(QDateTime const &when = QDateTime()) const;
577 
578         /**
579              * @brief calculateCulmination find culmination time adjust for the job offset
580              * @param when date and time to start searching from, now if omitted
581              * @return The date and time the target is in entering the culmination interval, valid if found, invalid if not achievable (currently always valid).
582              */
583         QDateTime calculateCulmination(QDateTime const &when = QDateTime()) const;
584 
585         /**
586              * @brief calculateDawnDusk find the next astronomical dawn and dusk after the current date and time of observation
587              */
588         static void calculateDawnDusk(QDateTime const &when, QDateTime &dawn, QDateTime &dusk);
589 
590         /**
591              * @brief getNextAstronomicalTwilightDawn
592              * @return a local time QDateTime locating the first astronomical dawn after this observation.
593              * @note The dawn time takes into account the Ekos dawn offset.
594              */
getDawnAstronomicalTwilight()595         QDateTime getDawnAstronomicalTwilight() const
596         {
597             return nextDawn;
598         };
599 
600         /**
601              * @brief getDuskAstronomicalTwilight
602              * @return a local-time QDateTime locating the first astronomical dusk after this observation.
603              * @note The dusk time takes into account the Ekos dusk offset.
604              */
getDuskAstronomicalTwilight()605         QDateTime getDuskAstronomicalTwilight() const
606         {
607             return nextDusk;
608         };
609 
610         /**
611              * @brief runsDuringAstronomicalNightTime
612              * @return true if the next dawn/dusk event after this observation is the astronomical dawn, else false.
613              * @note This function relies on the guarantee that dawn and dusk are calculated to be the first events after this observation.
614              */
615         bool runsDuringAstronomicalNightTime() const;
616 
617         /**
618              * @brief findAltitude Find altitude given a specific time
619              * @param target Target
620              * @param when date time to find altitude
621              * @param is_setting whether target is setting at the argument time (optional).
622              * @param debug outputs calculation to log file (optional).
623              * @return Altitude of the target at the specific date and time given.
624              * @warning This function uses the current KStars geolocation.
625              */
626         static double findAltitude(const SkyPoint &target, const QDateTime &when, bool *is_setting = nullptr, bool debug = false);
627 
628         /**
629              * @brief getMinAltitudeConstraint Find minimum allowed altitude for this job at the given azimuth.
630              * @param azimuth Azimuth
631              * @return Minimum allowed altitude of the target at the specific azimuth.
632              */
633         double getMinAltitudeConstraint(double azimuth) const;
634 
635         // Convenience debugging methods.
636         static QString jobStatusString(JOBStatus status);
637         static QString jobStageString(JOBStage stage);
638 
639     private:
640         // Private constructor for unit testing.
641         SchedulerJob(KSMoon *moonPtr);
642         friend TestSchedulerUnit;
643         friend TestEkosSchedulerOps;
644 
645         /** @brief Setter used in the unit test to fix the local time. Otherwise getter gets from KStars instance. */
646         /** @{ */
647         static KStarsDateTime getLocalTime();
setLocalTime(KStarsDateTime * time)648         static void setLocalTime(KStarsDateTime *time)
649         {
650             storedLocalTime = time;
651         }
hasLocalTime()652         static bool hasLocalTime()
653         {
654             return storedLocalTime != nullptr;
655         }
656         /** @} */
657 
658         /** @brief Setter used in testing to fix the geo location. Otherwise getter gets from KStars instance. */
659         /** @{ */
660         static const GeoLocation *getGeo();
setGeo(GeoLocation * geo)661         static void setGeo(GeoLocation *geo)
662         {
663             storedGeo = geo;
664         }
hasGeo()665         static bool hasGeo()
666         {
667             return storedGeo != nullptr;
668         }
669         /** @} */
670 
671         /** @brief Setter used in testing to fix the artificial horizon. Otherwise getter gets from KStars instance. */
672         /** @{ */
673         static const ArtificialHorizon *getHorizon();
setHorizon(ArtificialHorizon * horizon)674         static void setHorizon(ArtificialHorizon *horizon)
675         {
676             storedHorizon = horizon;
677         }
hasHorizon()678         static bool hasHorizon()
679         {
680             return storedHorizon != nullptr;
681         }
682 
683         /** @} */
684 
685         QString name;
686         SkyPoint targetCoords;
687         double rotation { -1 };
688         JOBStatus state { JOB_IDLE };
689         JOBStage stage { STAGE_IDLE };
690 
691         StartupCondition fileStartupCondition { START_ASAP };
692         StartupCondition startupCondition { START_ASAP };
693         CompletionCondition completionCondition { FINISH_SEQUENCE };
694 
695         int sequenceCount { 0 };
696         int completedCount { 0 };
697 
698         QDateTime fileStartupTime;
699         QDateTime startupTime;
700         QDateTime completionTime;
701 
702         /* @internal Caches to optimize cell rendering. */
703         /* @{ */
704         double altitudeAtStartup { 0 };
705         double altitudeAtCompletion { 0 };
706         bool isSettingAtStartup { false };
707         bool isSettingAtCompletion { false };
708         /* @} */
709 
710         QUrl sequenceFile;
711         QUrl fitsFile;
712 
713         double minAltitude { UNDEFINED_ALTITUDE };
714         double minMoonSeparation { -1 };
715 
716         bool enforceWeather { false };
717         bool enforceTwilight { false };
718         bool enforceArtificialHorizon { false };
719 
720         QDateTime nextDawn;
721         QDateTime nextDusk;
722 
723         StepPipeline stepPipeline { USE_NONE };
724 
725         /** @internal Widget cell/label shortcuts. */
726         /** @{ */
727         QTableWidgetItem *nameCell { nullptr };
728         QLabel *nameLabel { nullptr };
729         QTableWidgetItem *statusCell { nullptr };
730         QTableWidgetItem *stageCell { nullptr };
731         QLabel *stageLabel { nullptr };
732         QTableWidgetItem *altitudeCell { nullptr };
733         QTableWidgetItem *startupCell { nullptr };
734         QTableWidgetItem *completionCell { nullptr };
735         QTableWidgetItem *estimatedTimeCell { nullptr };
736         QTableWidgetItem *captureCountCell { nullptr };
737         QTableWidgetItem *scoreCell { nullptr };
738         QTableWidgetItem *leadTimeCell { nullptr };
739         /** @} */
740 
741         int score { 0 };
742         int16_t culminationOffset { 0 };
743         uint8_t priority { 10 };
744         int64_t estimatedTime { -1 };
745         int64_t leadTime { 0 };
746         uint16_t repeatsRequired { 1 };
747         uint16_t repeatsRemaining { 1 };
748         bool inSequenceFocus { false };
749 
750         QString dateTimeDisplayFormat;
751 
752         bool lightFramesRequired { false };
753 
754         QMap<QString, uint16_t> capturedFramesMap;
755 
756         /// Pointer to Moon object
757         KSMoon *moon { nullptr };
758 
759         // These are used in testing, instead of KStars::Instance() resources
760         static KStarsDateTime *storedLocalTime;
761         static GeoLocation *storedGeo;
762         static ArtificialHorizon *storedHorizon;
763 };
764