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