File indexing completed on 2024-04-28 03:43:45

0001 /*  Ekos Scheduler Job
0002     SPDX-FileCopyrightText: Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #pragma once
0008 
0009 #include "skypoint.h"
0010 #include "schedulertypes.h"
0011 
0012 #include <QUrl>
0013 #include <QMap>
0014 #include "kstarsdatetime.h"
0015 #include <QJsonObject>
0016 
0017 class ArtificialHorizon;
0018 class KSMoon;
0019 class TestSchedulerUnit;
0020 class TestEkosSchedulerOps;
0021 class dms;
0022 
0023 namespace Ekos
0024 {
0025 class SchedulerJob
0026 {
0027     public:
0028         SchedulerJob();
0029 
0030         QJsonObject toJson() const;
0031 
0032         /** @brief Actions that may be processed when running a SchedulerJob.
0033          * FIXME: StepPipeLine is actually a mask, change this into a bitfield.
0034          */
0035         typedef enum
0036         {
0037             USE_NONE  = 0,
0038             USE_TRACK = 1 << 0,
0039             USE_FOCUS = 1 << 1,
0040             USE_ALIGN = 1 << 2,
0041             USE_GUIDE = 1 << 3
0042         } StepPipeline;
0043 
0044         /** @brief Coordinates of the target of this job. */
0045         /** @{ */
0046         SkyPoint const &getTargetCoords() const
0047         {
0048             return targetCoords;
0049         }
0050         void setTargetCoords(const dms &ra, const dms &dec, double djd);
0051         /** @} */
0052 
0053         double getPositionAngle()
0054         {
0055             return m_PositionAngle;
0056         }
0057         void setPositionAngle(double value);
0058 
0059         /** @brief Capture sequence this job uses while running. */
0060         /** @{ */
0061         QUrl getSequenceFile() const
0062         {
0063             return sequenceFile;
0064         }
0065         void setSequenceFile(const QUrl &value);
0066         /** @} */
0067 
0068         /** @brief FITS file whose plate solve produces target coordinates. */
0069         /** @{ */
0070         QUrl getFITSFile() const
0071         {
0072             return fitsFile;
0073         }
0074         void setFITSFile(const QUrl &value);
0075         /** @} */
0076 
0077         /** @brief Minimal target altitude to process this job */
0078         /** @{ */
0079         double getMinAltitude() const
0080         {
0081             return minAltitude;
0082         }
0083         void setMinAltitude(const double &value);
0084         /** @} */
0085 
0086         /** @brief Does this job have a min-altitude parameter. */
0087         /** @{ */
0088         bool hasMinAltitude() const
0089         {
0090             return UNDEFINED_ALTITUDE < minAltitude;
0091         }
0092         static constexpr int UNDEFINED_ALTITUDE = -90;
0093         /** @} */
0094 
0095         /** @brief Does this job have any altitude constraints. */
0096         /** @{ */
0097         bool hasAltitudeConstraint() const;
0098         /** @} */
0099 
0100         /** @brief Minimal Moon separation to process this job. */
0101         /** @{ */
0102         double getMinMoonSeparation() const
0103         {
0104             return minMoonSeparation;
0105         }
0106         void setMinMoonSeparation(const double &value);
0107         /** @} */
0108 
0109         /** @brief Whether to restrict this job to good weather. */
0110         /** @{ */
0111         bool getEnforceWeather() const
0112         {
0113             return enforceWeather;
0114         }
0115         void setEnforceWeather(bool value);
0116         /** @} */
0117 
0118         /** @brief Mask of actions to process for this job. */
0119         /** @{ */
0120         StepPipeline getStepPipeline() const
0121         {
0122             return stepPipeline;
0123         }
0124         void setStepPipeline(const StepPipeline &value);
0125         /** @} */
0126 
0127         /** @brief Condition under which this job starts. */
0128         /** @{ */
0129         StartupCondition getStartupCondition() const
0130         {
0131             return startupCondition;
0132         }
0133         void setStartupCondition(const StartupCondition &value);
0134         /** @} */
0135 
0136         /** @brief Condition under which this job completes. */
0137         /** @{ */
0138         CompletionCondition getCompletionCondition() const
0139         {
0140             return completionCondition;
0141         }
0142         void setCompletionCondition(const CompletionCondition &value);
0143         /** @} */
0144 
0145         /** @brief Original startup condition, as entered by the user. */
0146         /** @{ */
0147         StartupCondition getFileStartupCondition() const
0148         {
0149             return fileStartupCondition;
0150         }
0151         void setFileStartupCondition(const StartupCondition &value);
0152         /** @} */
0153 
0154         /** @brief Original time at which the job must start, as entered by the user. */
0155         /** @{ */
0156         QDateTime getFileStartupTime() const
0157         {
0158             return fileStartupTime;
0159         }
0160         void setFileStartupTime(const QDateTime &value);
0161         /** @} */
0162 
0163         /** @brief Whether this job requires re-focus while running its capture sequence. */
0164         /** @{ */
0165         bool getInSequenceFocus() const
0166         {
0167             return inSequenceFocus;
0168         }
0169         void setInSequenceFocus(bool value);
0170         /** @} */
0171 
0172         /** @brief Whether to restrict job to night time. */
0173         /** @{ */
0174         bool getEnforceTwilight() const
0175         {
0176             return enforceTwilight;
0177         }
0178         void setEnforceTwilight(bool value);
0179         /** @} */
0180 
0181         /** @brief Whether to restrict job to night time. */
0182         /** @{ */
0183         bool getEnforceArtificialHorizon() const
0184         {
0185             return enforceArtificialHorizon;
0186         }
0187         void setEnforceArtificialHorizon(bool value);
0188         /** @} */
0189 
0190         /** @brief Current name of the scheduler job. */
0191         /** @{ */
0192         QString getName() const
0193         {
0194             return name;
0195         }
0196         void setName(const QString &value);
0197         /** @} */
0198 
0199         /** @brief Group the scheduler job belongs to. */
0200         /** @{ */
0201         QString getGroup() const
0202         {
0203             return group;
0204         }
0205         void setGroup(const QString &value);
0206         /** @} */
0207 
0208         /** @brief Iteration the scheduler job has achieved. This only makes sense for jobs that repeat. */
0209         /** @{ */
0210         int getCompletedIterations() const
0211         {
0212             return completedIterations;
0213         }
0214         void setCompletedIterations(int value);
0215         /** @} */
0216 
0217         /** @brief Current state of the scheduler job.
0218          * Setting state to JOB_ABORTED automatically resets the startup characteristics.
0219          * Setting state to JOB_INVALID automatically resets the startup characteristics and the duration estimation.
0220          * @see SchedulerJob::setStartupCondition, SchedulerJob::setFileStartupCondition, SchedulerJob::setStartupTime
0221          * and SchedulerJob::setFileStartupTime.
0222          */
0223         /** @{ */
0224         SchedulerJobStatus getState() const
0225         {
0226             return state;
0227         }
0228         QDateTime getStateTime() const
0229         {
0230             return stateTime;
0231         }
0232         QDateTime getLastAbortTime() const
0233         {
0234             return lastAbortTime;
0235         }
0236         QDateTime getLastErrorTime() const
0237         {
0238             return lastErrorTime;
0239         }
0240         void setState(const SchedulerJobStatus &value);
0241         /** @} */
0242 
0243         /** @brief Current stage of the scheduler job. */
0244         /** @{ */
0245         SchedulerJobStage getStage() const
0246         {
0247             return stage;
0248         }
0249         void setStage(const SchedulerJobStage &value);
0250         /** @} */
0251 
0252         /** @brief Number of captures required in the associated sequence. */
0253         /** @{ */
0254         int getSequenceCount() const
0255         {
0256             return sequenceCount;
0257         }
0258         void setSequenceCount(const int count);
0259         /** @} */
0260 
0261         /** @brief Number of captures completed in the associated sequence. */
0262         /** @{ */
0263         int getCompletedCount() const
0264         {
0265             return completedCount;
0266         }
0267         void setCompletedCount(const int count);
0268         /** @} */
0269 
0270         /** @brief Time at which the job must start. */
0271         /** @{ */
0272         QDateTime getStartupTime() const
0273         {
0274             return startupTime;
0275         }
0276         void setStartupTime(const QDateTime &value);
0277         /** @} */
0278 
0279         /** @brief Time after which the job is considered complete. */
0280         /** @{ */
0281         QDateTime getCompletionTime() const
0282         {
0283             return completionTime;
0284         }
0285         QDateTime getGreedyCompletionTime() const
0286         {
0287             return greedyCompletionTime;
0288         }
0289         const QString &getStopReason() const
0290         {
0291             return stopReason;
0292         }
0293         void setStopReason(const QString &reason)
0294         {
0295             stopReason = reason;
0296         }
0297         void setCompletionTime(const QDateTime &value);
0298         /** @} */
0299         void setGreedyCompletionTime(const QDateTime &value);
0300 
0301         /** @brief Estimation of the time the job will take to process. */
0302         /** @{ */
0303         int64_t getEstimatedTime() const
0304         {
0305             return estimatedTime;
0306         }
0307         void setEstimatedTime(const int64_t &value);
0308         /** @} */
0309 
0310         /** @brief Estimation of the time the job will take to process each repeat. */
0311         /** @{ */
0312         int64_t getEstimatedTimePerRepeat() const
0313         {
0314             return estimatedTimePerRepeat;
0315         }
0316         void setEstimatedTimePerRepeat(const int64_t &value)
0317         {
0318             estimatedTimePerRepeat = value;
0319         }
0320 
0321         /** @brief Estimation of the time the job will take at startup. */
0322         /** @{ */
0323         int64_t getEstimatedStartupTime() const
0324         {
0325             return estimatedStartupTime;
0326         }
0327         void setEstimatedStartupTime(const int64_t &value)
0328         {
0329             estimatedStartupTime = value;
0330         }
0331 
0332         /** @brief Estimation of the time the job will take to process each repeat. */
0333         /** @{ */
0334         int64_t getEstimatedTimeLeftThisRepeat() const
0335         {
0336             return estimatedTimeLeftThisRepeat;
0337         }
0338         void setEstimatedTimeLeftThisRepeat(const int64_t &value)
0339         {
0340             estimatedTimeLeftThisRepeat = value;
0341         }
0342 
0343         /** @brief Whether this job requires light frames, or only calibration frames. */
0344         /** @{ */
0345         bool getLightFramesRequired() const
0346         {
0347             return lightFramesRequired;
0348         }
0349         void setLightFramesRequired(bool value);
0350         /** @} */
0351 
0352         /** @brief Number of times this job must be repeated (in terms of capture count). */
0353         /** @{ */
0354         uint16_t getRepeatsRequired() const
0355         {
0356             return repeatsRequired;
0357         }
0358         void setRepeatsRequired(const uint16_t &value);
0359         /** @} */
0360 
0361         /** @brief Number of times this job still has to be repeated (in terms of capture count). */
0362         /** @{ */
0363         uint16_t getRepeatsRemaining() const
0364         {
0365             return repeatsRemaining;
0366         }
0367         void setRepeatsRemaining(const uint16_t &value);
0368         /** @} */
0369 
0370         /** @brief The map of capture counts for this job, keyed by its capture storage signatures. */
0371         /** @{ */
0372         const CapturedFramesMap &getCapturedFramesMap() const
0373         {
0374             return capturedFramesMap;
0375         }
0376         void setCapturedFramesMap(const CapturedFramesMap &value);
0377         /** @} */
0378 
0379         /** @brief Resetting a job to original values:
0380          * - idle state and stage
0381          * - original startup, none if asap, else user original setting
0382          * - duration not estimated
0383          * - full repeat count
0384          */
0385         void reset();
0386 
0387         /** @brief Determining whether a SchedulerJob is a duplicate of another.
0388          * @param a_job is the other SchedulerJob to test duplication against.
0389          * @return True if objects are different, but name and sequence file are identical, else false.
0390          * @warning This is a weak comparison, but that's what the scheduler looks at to decide completion.
0391          */
0392         bool isDuplicateOf(SchedulerJob const *a_job) const
0393         {
0394             return this != a_job && name == a_job->name && sequenceFile == a_job->sequenceFile;
0395         }
0396 
0397         /** @brief Compare ::SchedulerJob instances based on altitude and movement in sky at startup time.
0398          * @todo This is a qSort predicate, deprecated in QT5.
0399          * @arg a, b are ::SchedulerJob instances to compare.
0400          * @arg when is the date/time to use to calculate the altitude to sort with, defaulting to a's startup time.
0401          * @note To obtain proper sort between several SchedulerJobs, all should have the same startup time.
0402          * @note Use std:bind to bind a specific date/time to this predicate for altitude calculation.
0403          * @return true is a is setting but not b.
0404          * @return false if b is setting but not a.
0405          * @return true otherwise, if the altitude of b is lower than the altitude of a.
0406          * @return false otherwise, if the altitude of b is higher than or equal to the altitude of a.
0407          */
0408         static bool decreasingAltitudeOrder(SchedulerJob const *a, SchedulerJob const *b, QDateTime const &when = QDateTime());
0409 
0410         /**
0411              * @brief moonSeparationOK return true if angle from target to the Moon is > minMoonSeparation
0412              * @param when date and time to check the Moon separation, now if omitted.
0413              * @return true if target is separated enough from the Moon.
0414              */
0415         bool moonSeparationOK(QDateTime const &when = QDateTime()) const;
0416 
0417         /**
0418              * @brief calculateNextTime calculate the next time constraints are met (or missed).
0419              * @param when date and time to start searching from, now if omitted.
0420              * @param constraintsAreMet if true, searches for the next time constrains are met, else missed.
0421              * @return The date and time the target meets or misses constraints.
0422              */
0423         QDateTime calculateNextTime(QDateTime const &when, bool checkIfConstraintsAreMet = true, int increment = 1,
0424                                     QString *reason = nullptr, bool runningJob = false, const QDateTime &until = QDateTime()) const;
0425         QDateTime getNextPossibleStartTime(const QDateTime &when, int increment = 1, bool runningJob = false,
0426                                            const QDateTime &until = QDateTime()) const;
0427         QDateTime getNextEndTime(const QDateTime &start, int increment = 1, QString *reason = nullptr,
0428                                  const QDateTime &until = QDateTime()) const;
0429 
0430         /**
0431              * @brief getNextAstronomicalTwilightDawn
0432              * @return a local time QDateTime locating the first astronomical dawn after this observation.
0433              * @note The dawn time takes into account the Ekos dawn offset.
0434              */
0435         QDateTime getDawnAstronomicalTwilight() const
0436         {
0437             return nextDawn;
0438         };
0439 
0440         /**
0441              * @brief getDuskAstronomicalTwilight
0442              * @return a local-time QDateTime locating the first astronomical dusk after this observation.
0443              * @note The dusk time takes into account the Ekos dusk offset.
0444              */
0445         QDateTime getDuskAstronomicalTwilight() const
0446         {
0447             return nextDusk;
0448         };
0449 
0450         /**
0451              * @brief runsDuringAstronomicalNightTime
0452              * @param time uses the time given for the check, or, if not valid (the default) uses the job's startup time.
0453              * @return true if the next dawn/dusk event after this observation is the astronomical dawn, else false.
0454              * @note This function relies on the guarantee that dawn and dusk are calculated to be the first events after this observation.
0455              */
0456         bool runsDuringAstronomicalNightTime(const QDateTime &time = QDateTime(), QDateTime *nextPossibleSuccess = nullptr) const;
0457 
0458         /**
0459              * @brief satisfiesAltitudeConstraint sees if altitude is allowed for this job at the given azimuth.
0460              * @param azimuth Azimuth
0461              * @param altitude Altitude
0462              * @param altitudeReason a human-readable string explaining why false was returned.
0463             * @return true if this altitude is permissible for this job
0464              */
0465         bool satisfiesAltitudeConstraint(double azimuth, double altitude, QString *altitudeReason = nullptr) const;
0466 
0467         /**
0468          * @brief setInitialFilter Set initial filter used in the capture sequence. This is used to pass to focus module.
0469          * @param value Filter name of FIRST LIGHT job in the sequence queue, if any.
0470          */
0471         void setInitialFilter(const QString &value);
0472         const QString &getInitialFilter() const;
0473 
0474         // Convenience debugging methods.
0475         static QString jobStatusString(SchedulerJobStatus status);
0476         static QString jobStageString(SchedulerJobStage stage);
0477         QString jobStartupConditionString(StartupCondition condition) const;
0478         QString jobCompletionConditionString(CompletionCondition condition) const;
0479 
0480         // Clear the cache that keeps results for getNextPossibleStartTime().
0481         void clearCache()
0482         {
0483             startTimeCache.clear();
0484         }
0485         double getAltitudeAtStartup() const
0486         {
0487             return altitudeAtStartup;
0488         }
0489         double getAltitudeAtCompletion() const
0490         {
0491             return altitudeAtCompletion;
0492         }
0493         bool isSettingAtStartup() const
0494         {
0495             return settingAtStartup;
0496         }
0497         bool isSettingAtCompletion() const
0498         {
0499             return settingAtCompletion;
0500         }
0501 
0502 private:
0503         bool runsDuringAstronomicalNightTimeInternal(const QDateTime &time, QDateTime *minDawnDusk,
0504                 QDateTime *nextPossibleSuccess = nullptr) const;
0505 
0506         // Private constructor for unit testing.
0507         SchedulerJob(KSMoon *moonPtr);
0508         friend TestSchedulerUnit;
0509         friend TestEkosSchedulerOps;
0510 
0511         /** @brief Setter used in the unit test to fix the local time. Otherwise getter gets from KStars instance. */
0512         /** @{ */
0513         static KStarsDateTime getLocalTime();
0514         /** @} */
0515 
0516         /** @brief Setter used in testing to fix the artificial horizon. Otherwise getter gets from KStars instance. */
0517         /** @{ */
0518         static const ArtificialHorizon *getHorizon();
0519         static void setHorizon(ArtificialHorizon *horizon)
0520         {
0521             storedHorizon = horizon;
0522         }
0523         static bool hasHorizon()
0524         {
0525             return storedHorizon != nullptr;
0526         }
0527 
0528         /** @} */
0529 
0530         QString name;
0531         QString group;
0532         int completedIterations { 0 };
0533         SkyPoint targetCoords;
0534         double m_PositionAngle { -1 };
0535         SchedulerJobStatus state { SCHEDJOB_IDLE };
0536         SchedulerJobStage stage { SCHEDSTAGE_IDLE };
0537 
0538         // The time that the job stage was set.
0539         // Used by the Greedy Algorithm to decide when to run JOB_ABORTED jobs again.
0540         QDateTime stateTime;
0541         QDateTime lastAbortTime;
0542         QDateTime lastErrorTime;
0543 
0544         StartupCondition fileStartupCondition { START_ASAP };
0545         StartupCondition startupCondition { START_ASAP };
0546         CompletionCondition completionCondition { FINISH_SEQUENCE };
0547 
0548         int sequenceCount { 0 };
0549         int completedCount { 0 };
0550 
0551         QDateTime fileStartupTime;
0552         QDateTime startupTime;
0553         QDateTime completionTime;
0554         // An alternative completionTime field used by the greedy scheduler algorithm.
0555         QDateTime greedyCompletionTime;
0556         // The reason this job is stopping/will-be stopped.
0557         QString stopReason;
0558 
0559         /* @internal Caches to optimize cell rendering. */
0560         /* @{ */
0561         double altitudeAtStartup { 0 };
0562         double altitudeAtCompletion { 0 };
0563         bool settingAtStartup { false };
0564         bool settingAtCompletion { false };
0565         /* @} */
0566 
0567         QUrl sequenceFile;
0568         QUrl fitsFile;
0569 
0570         double minAltitude { UNDEFINED_ALTITUDE };
0571         double minMoonSeparation { -1 };
0572 
0573         bool enforceWeather { false };
0574         bool enforceTwilight { false };
0575         bool enforceArtificialHorizon { false };
0576 
0577         QDateTime nextDawn;
0578         QDateTime nextDusk;
0579 
0580         StepPipeline stepPipeline { USE_NONE };
0581 
0582         int64_t estimatedTime { -1 };
0583         int64_t estimatedTimePerRepeat { 0 };
0584         int64_t estimatedStartupTime { 0 };
0585         int64_t estimatedTimeLeftThisRepeat { 0 };
0586         uint16_t repeatsRequired { 1 };
0587         uint16_t repeatsRemaining { 1 };
0588         bool inSequenceFocus { false };
0589         QString m_InitialFilter;
0590 
0591         QString dateTimeDisplayFormat;
0592 
0593         bool lightFramesRequired { false };
0594 
0595         QMap<QString, uint16_t> capturedFramesMap;
0596 
0597         /// Pointer to Moon object
0598         KSMoon *moon { nullptr };
0599 
0600         // This class is used to cache the results computed in getNextPossibleStartTime()
0601         // which is called repeatedly by the Greedy scheduler.
0602         // The cache would need to be cleared if something changes that would affect the
0603         // start time of jobs (geography, altitide constraints) so it is reset at the start
0604         // of all schedule calculations--which does not impact its effectiveness.
0605         class StartTimeCache
0606         {
0607                 // Keep track of calls to getNextPossibleStartTime, storing the when, until and result.
0608                 struct StartTimeComputation
0609                 {
0610                     QDateTime from;
0611                     QDateTime until;
0612                     QDateTime result;
0613                 };
0614             public:
0615                 StartTimeCache() {}
0616                 // Check if the computation has been done, and if so, return the previous result.
0617                 bool check(const QDateTime &from, const QDateTime &until,
0618                            QDateTime *result, QDateTime *newFrom) const;
0619                 // Add a result to the cache.
0620                 void add(const QDateTime &from, const QDateTime &until, const QDateTime &result) const;
0621                 // Clear the cache.
0622                 void clear() const;
0623             private:
0624                 // Made this mutable and all methods const so that the cache could be
0625                 // used in SchedulerJob const methods.
0626                 mutable QList<StartTimeComputation> startComputations;
0627         };
0628         StartTimeCache startTimeCache;
0629 
0630         // These are used in testing, instead of KStars::Instance() resources
0631         static KStarsDateTime *storedLocalTime;
0632         static GeoLocation *storedGeo;
0633         static ArtificialHorizon *storedHorizon;
0634 };
0635 } // Ekos namespace