Kstars

schedulerjob.h
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#include "schedulertypes.h"
11#include "ekos/capture/sequencejob.h"
12
13#include <QUrl>
14#include <QMap>
15#include "kstarsdatetime.h"
16#include <QJsonObject>
17
19class KSMoon;
20class TestSchedulerUnit;
21class TestEkosSchedulerOps;
22class dms;
23
24namespace Ekos
25{
26class SchedulerJob
27{
28 public:
29 SchedulerJob();
30
31 QJsonObject toJson() const;
32
33 /** @brief Actions that may be processed when running a SchedulerJob.
34 * FIXME: StepPipeLine is actually a mask, change this into a bitfield.
35 */
36 typedef enum
37 {
38 USE_NONE = 0,
39 USE_TRACK = 1 << 0,
40 USE_FOCUS = 1 << 1,
41 USE_ALIGN = 1 << 2,
42 USE_GUIDE = 1 << 3
43 } StepPipeline;
44
45 struct JobProgress
46 {
47 int numCompleted = 0;
48 CCDFrameType type;
49 bool isDarkFlat = false;
51 JobProgress() {}
52 JobProgress(int numCompleted, const SequenceJob *job)
53 : numCompleted(numCompleted), type(job->getFrameType()),
54 isDarkFlat(job->jobType() == SequenceJob::JOBTYPE_DARKFLAT),
55 properties(job->getCoreProperties()) {};
56 };
57
58 /** @brief Coordinates of the target of this job. */
59 /** @{ */
60 SkyPoint const &getTargetCoords() const
61 {
62 return targetCoords;
63 }
64 void setTargetCoords(const dms &ra, const dms &dec, double djd);
65 /** @} */
66
67 double getPositionAngle()
68 {
69 return m_PositionAngle;
70 }
71 void setPositionAngle(double value);
72
73 /** @brief Capture sequence this job uses while running. */
74 /** @{ */
75 QUrl getSequenceFile() const
76 {
77 return sequenceFile;
78 }
79 void setSequenceFile(const QUrl &value);
80 /** @} */
81
82 /** @brief FITS file whose plate solve produces target coordinates. */
83 /** @{ */
84 QUrl getFITSFile() const
85 {
86 return fitsFile;
87 }
88 void setFITSFile(const QUrl &value);
89 /** @} */
90
91 /** @brief Minimal target altitude to process this job */
92 /** @{ */
93 double getMinAltitude() const
94 {
95 return minAltitude;
96 }
97 void setMinAltitude(const double &value);
98 /** @} */
99
100 /** @brief Does this job have a min-altitude parameter. */
101 /** @{ */
102 bool hasMinAltitude() const
103 {
104 return UNDEFINED_ALTITUDE < minAltitude;
105 }
106 static constexpr int UNDEFINED_ALTITUDE = -90;
107 /** @} */
108
109 /** @brief Does this job have any altitude constraints. */
110 /** @{ */
111 bool hasAltitudeConstraint() const;
112 /** @} */
113
114 /** @brief Minimal Moon separation to process this job. */
115 /** @{ */
116 double getMinMoonSeparation() const
117 {
118 return minMoonSeparation;
119 }
120 void setMinMoonSeparation(const double &value);
121 /** @} */
122
123 /** @brief Whether to restrict this job to good weather. */
124 /** @{ */
125 bool getEnforceWeather() const
126 {
127 return enforceWeather;
128 }
129 void setEnforceWeather(bool value);
130 /** @} */
131
132 /** @brief Mask of actions to process for this job. */
133 /** @{ */
134 StepPipeline getStepPipeline() const
135 {
136 return stepPipeline;
137 }
138 void setStepPipeline(const StepPipeline &value);
139 /** @} */
140
141 /** @brief Condition under which this job starts. */
142 /** @{ */
143 StartupCondition getStartupCondition() const
144 {
145 return startupCondition;
146 }
147 void setStartupCondition(const StartupCondition &value);
148 /** @} */
149
150 /** @brief Condition under which this job completes. */
151 /** @{ */
152 CompletionCondition getCompletionCondition() const
153 {
154 return completionCondition;
155 }
156 void setCompletionCondition(const CompletionCondition &value);
157 /** @} */
158
159 /** @brief Original startup condition, as entered by the user. */
160 /** @{ */
161 StartupCondition getFileStartupCondition() const
162 {
163 return fileStartupCondition;
164 }
165 void setFileStartupCondition(const StartupCondition &value);
166 /** @} */
167
168 /** @brief Original time at which the job must start, as entered by the user. */
169 /** @{ */
170 QDateTime getFileStartupTime() const
171 {
172 return fileStartupTime;
173 }
174 void setFileStartupTime(const QDateTime &value);
175 /** @} */
176
177 /** @brief Whether this job requires re-focus while running its capture sequence. */
178 /** @{ */
179 bool getInSequenceFocus() const
180 {
181 return inSequenceFocus;
182 }
183 void setInSequenceFocus(bool value);
184 /** @} */
185
186 /** @brief Whether to restrict job to night time. */
187 /** @{ */
188 bool getEnforceTwilight() const
189 {
190 return enforceTwilight;
191 }
192 void setEnforceTwilight(bool value);
193 /** @} */
194
195 /** @brief Whether to restrict job to night time. */
196 /** @{ */
197 bool getEnforceArtificialHorizon() const
198 {
199 return enforceArtificialHorizon;
200 }
201 void setEnforceArtificialHorizon(bool value);
202 /** @} */
203
204 /** @brief Current name of the scheduler job. */
205 /** @{ */
206 QString getName() const
207 {
208 return name;
209 }
210 void setName(const QString &value);
211 /** @} */
212
213 /** @brief Group the scheduler job belongs to. */
214 /** @{ */
215 QString getGroup() const
216 {
217 return group;
218 }
219 void setGroup(const QString &value);
220 /** @} */
221
222 /** @brief Iteration the scheduler job has achieved. This only makes sense for jobs that repeat. */
223 /** @{ */
224 int getCompletedIterations() const
225 {
226 return completedIterations;
227 }
228 void setCompletedIterations(int value);
229 /** @} */
230
231 /** @brief Current state of the scheduler job.
232 * Setting state to JOB_ABORTED automatically resets the startup characteristics.
233 * Setting state to JOB_INVALID automatically resets the startup characteristics and the duration estimation.
234 * @see SchedulerJob::setStartupCondition, SchedulerJob::setFileStartupCondition, SchedulerJob::setStartupTime
235 * and SchedulerJob::setFileStartupTime.
236 */
237 /** @{ */
238 SchedulerJobStatus getState() const
239 {
240 return state;
241 }
242 QDateTime getStateTime() const
243 {
244 return stateTime;
245 }
246 QDateTime getLastAbortTime() const
247 {
248 return lastAbortTime;
249 }
250 QDateTime getLastErrorTime() const
251 {
252 return lastErrorTime;
253 }
254 void setState(const SchedulerJobStatus &value);
255 /** @} */
256
257 /** @brief Current stage of the scheduler job. */
258 /** @{ */
259 SchedulerJobStage getStage() const
260 {
261 return stage;
262 }
263 void setStage(const SchedulerJobStage &value);
264 /** @} */
265
266 /** @brief Number of captures required in the associated sequence. */
267 /** @{ */
268 int getSequenceCount() const
269 {
270 return sequenceCount;
271 }
272 void setSequenceCount(const int count);
273 /** @} */
274
275 /** @brief Number of captures completed in the associated sequence. */
276 /** @{ */
277 int getCompletedCount() const
278 {
279 return completedCount;
280 }
281 void setCompletedCount(const int count);
282 /** @} */
283
284 /** @brief Time at which the job must start. */
285 /** @{ */
286 QDateTime getStartupTime() const
287 {
288 return startupTime;
289 }
290 void setStartupTime(const QDateTime &value);
291 /** @} */
292
293 /** @brief Time after which the job is considered complete. */
294 /** @{ */
295 QDateTime getCompletionTime() const
296 {
297 return completionTime;
298 }
299 QDateTime getGreedyCompletionTime() const
300 {
301 return greedyCompletionTime;
302 }
303 const QString &getStopReason() const
304 {
305 return stopReason;
306 }
307 void setStopReason(const QString &reason)
308 {
309 stopReason = reason;
310 }
311 void setCompletionTime(const QDateTime &value);
312 /** @} */
313 void setGreedyCompletionTime(const QDateTime &value);
314
315 /** @brief Estimation of the time the job will take to process. */
316 /** @{ */
317 int64_t getEstimatedTime() const
318 {
319 return estimatedTime;
320 }
321 void setEstimatedTime(const int64_t &value);
322 /** @} */
323
324 /** @brief Estimation of the time the job will take to process each repeat. */
325 /** @{ */
326 int64_t getEstimatedTimePerRepeat() const
327 {
328 return estimatedTimePerRepeat;
329 }
330 void setEstimatedTimePerRepeat(const int64_t &value)
331 {
332 estimatedTimePerRepeat = value;
333 }
334
335 /** @brief Estimation of the time the job will take at startup. */
336 /** @{ */
337 int64_t getEstimatedStartupTime() const
338 {
339 return estimatedStartupTime;
340 }
341 void setEstimatedStartupTime(const int64_t &value)
342 {
343 estimatedStartupTime = value;
344 }
345
346 /** @brief Estimation of the time the job will take to process each repeat. */
347 /** @{ */
348 int64_t getEstimatedTimeLeftThisRepeat() const
349 {
350 return estimatedTimeLeftThisRepeat;
351 }
352 void setEstimatedTimeLeftThisRepeat(const int64_t &value)
353 {
354 estimatedTimeLeftThisRepeat = value;
355 }
356
357 /** @brief Whether this job requires light frames, or only calibration frames. */
358 /** @{ */
359 bool getLightFramesRequired() const
360 {
361 return lightFramesRequired;
362 }
363 void setLightFramesRequired(bool value);
364 /** @} */
365
366 /** @brief Whether this job contains a calibration job that requires mount parking. */
367 /** @{ */
368 bool getCalibrationMountPark() const
369 {
370 return m_CalibrationMountPark;
371 }
372 void setCalibrationMountPark(bool value);
373 /** @} */
374
375 /** @brief Number of times this job must be repeated (in terms of capture count). */
376 /** @{ */
377 uint16_t getRepeatsRequired() const
378 {
379 return repeatsRequired;
380 }
381 void setRepeatsRequired(const uint16_t &value);
382 /** @} */
383
384 /** @brief Number of times this job still has to be repeated (in terms of capture count). */
385 /** @{ */
386 uint16_t getRepeatsRemaining() const
387 {
388 return repeatsRemaining;
389 }
390 void setRepeatsRemaining(const uint16_t &value);
391 /** @} */
392
393 void clearProgress()
394 {
395 m_Progress.clear();
396 }
397 void addProgress(int numCompleted, const SequenceJob *job)
398 {
399 m_Progress.append(JobProgress(numCompleted, job));
400 }
401 /** @brief Human-readable summary of captures completed for this job. */
402 /** @{ */
403 const QString getProgressSummary() const;
404 /** @} */
405
406 /** @brief The map of capture counts for this job, keyed by its capture storage signatures. */
407 /** @{ */
408 const CapturedFramesMap &getCapturedFramesMap() const
409 {
410 return capturedFramesMap;
411 }
412 void setCapturedFramesMap(const CapturedFramesMap &value);
413 /** @} */
414
415 /** @brief Resetting a job to original values:
416 * - idle state and stage
417 * - original startup, none if asap, else user original setting
418 * - duration not estimated
419 * - full repeat count
420 */
421 void reset();
422
423 /** @brief Determining whether a SchedulerJob is a duplicate of another.
424 * @param a_job is the other SchedulerJob to test duplication against.
425 * @return True if objects are different, but name and sequence file are identical, else false.
426 * @warning This is a weak comparison, but that's what the scheduler looks at to decide completion.
427 */
428 bool isDuplicateOf(SchedulerJob const *a_job) const
429 {
430 return this != a_job && name == a_job->name && sequenceFile == a_job->sequenceFile;
431 }
432
433 /** @brief Compare ::SchedulerJob instances based on altitude and movement in sky at startup time.
434 * @todo This is a qSort predicate, deprecated in QT5.
435 * @arg a, b are ::SchedulerJob instances to compare.
436 * @arg when is the date/time to use to calculate the altitude to sort with, defaulting to a's startup time.
437 * @note To obtain proper sort between several SchedulerJobs, all should have the same startup time.
438 * @note Use std:bind to bind a specific date/time to this predicate for altitude calculation.
439 * @return true is a is setting but not b.
440 * @return false if b is setting but not a.
441 * @return true otherwise, if the altitude of b is lower than the altitude of a.
442 * @return false otherwise, if the altitude of b is higher than or equal to the altitude of a.
443 */
444 static bool decreasingAltitudeOrder(SchedulerJob const *a, SchedulerJob const *b, QDateTime const &when = QDateTime());
445
446 /**
447 * @brief moonSeparationOK return true if angle from target to the Moon is > minMoonSeparation
448 * @param when date and time to check the Moon separation, now if omitted.
449 * @return true if target is separated enough from the Moon.
450 */
451 bool moonSeparationOK(QDateTime const &when = QDateTime()) const;
452
453 /**
454 * @brief calculateNextTime calculate the next time constraints are met (or missed).
455 * @param when date and time to start searching from, now if omitted.
456 * @param constraintsAreMet if true, searches for the next time constrains are met, else missed.
457 * @return The date and time the target meets or misses constraints.
458 */
459 QDateTime calculateNextTime(QDateTime const &when, bool checkIfConstraintsAreMet = true, int increment = 1,
460 QString *reason = nullptr, bool runningJob = false, const QDateTime &until = QDateTime()) const;
461 QDateTime getNextPossibleStartTime(const QDateTime &when, int increment = 1, bool runningJob = false,
462 const QDateTime &until = QDateTime()) const;
463 QDateTime getNextEndTime(const QDateTime &start, int increment = 1, QString *reason = nullptr,
464 const QDateTime &until = QDateTime()) const;
465
466 /**
467 * @brief getNextAstronomicalTwilightDawn
468 * @return a local time QDateTime locating the first astronomical dawn after this observation.
469 * @note The dawn time takes into account the Ekos dawn offset.
470 */
471 QDateTime getDawnAstronomicalTwilight() const
472 {
473 return nextDawn;
474 };
475
476 /**
477 * @brief getDuskAstronomicalTwilight
478 * @return a local-time QDateTime locating the first astronomical dusk after this observation.
479 * @note The dusk time takes into account the Ekos dusk offset.
480 */
481 QDateTime getDuskAstronomicalTwilight() const
482 {
483 return nextDusk;
484 };
485
486 /**
487 * @brief runsDuringAstronomicalNightTime
488 * @param time uses the time given for the check, or, if not valid (the default) uses the job's startup time.
489 * @return true if the next dawn/dusk event after this observation is the astronomical dawn, else false.
490 * @note This function relies on the guarantee that dawn and dusk are calculated to be the first events after this observation.
491 */
492 bool runsDuringAstronomicalNightTime(const QDateTime &time = QDateTime(), QDateTime *nextPossibleSuccess = nullptr) const;
493
494 /**
495 * @brief satisfiesAltitudeConstraint sees if altitude is allowed for this job at the given azimuth.
496 * @param azimuth Azimuth
497 * @param altitude Altitude
498 * @param altitudeReason a human-readable string explaining why false was returned.
499 * @return true if this altitude is permissible for this job
500 */
501 bool satisfiesAltitudeConstraint(double azimuth, double altitude, QString *altitudeReason = nullptr) const;
502
503 /**
504 * @brief setInitialFilter Set initial filter used in the capture sequence. This is used to pass to focus module.
505 * @param value Filter name of FIRST LIGHT job in the sequence queue, if any.
506 */
507 void setInitialFilter(const QString &value);
508 const QString &getInitialFilter() const;
509
510 // Convenience debugging methods.
511 static QString jobStatusString(SchedulerJobStatus status);
512 static QString jobStageString(SchedulerJobStage stage);
513 QString jobStartupConditionString(StartupCondition condition) const;
514 QString jobCompletionConditionString(CompletionCondition condition) const;
515
516 // Clear the cache that keeps results for getNextPossibleStartTime().
517 void clearCache()
518 {
519 startTimeCache.clear();
520 }
521 double getAltitudeAtStartup() const
522 {
523 return altitudeAtStartup;
524 }
525 double getAltitudeAtCompletion() const
526 {
527 return altitudeAtCompletion;
528 }
529 bool isSettingAtStartup() const
530 {
531 return settingAtStartup;
532 }
533 bool isSettingAtCompletion() const
534 {
535 return settingAtCompletion;
536 }
537
538private:
539 bool runsDuringAstronomicalNightTimeInternal(const QDateTime &time, QDateTime *minDawnDusk,
540 QDateTime *nextPossibleSuccess = nullptr) const;
541
542 // Private constructor for unit testing.
543 SchedulerJob(KSMoon *moonPtr);
544 friend TestSchedulerUnit;
545 friend TestEkosSchedulerOps;
546
547 /** @brief Setter used in the unit test to fix the local time. Otherwise getter gets from KStars instance. */
548 /** @{ */
549 static KStarsDateTime getLocalTime();
550 /** @} */
551
552 /** @brief Setter used in testing to fix the artificial horizon. Otherwise getter gets from KStars instance. */
553 /** @{ */
554 static const ArtificialHorizon *getHorizon();
555 static void setHorizon(ArtificialHorizon *horizon)
556 {
557 storedHorizon = horizon;
558 }
559 static bool hasHorizon()
560 {
561 return storedHorizon != nullptr;
562 }
563
564 /** @} */
565
566 QString name;
567 QString group;
568 int completedIterations { 0 };
569 SkyPoint targetCoords;
570 double m_PositionAngle { -1 };
571 SchedulerJobStatus state { SCHEDJOB_IDLE };
572 SchedulerJobStage stage { SCHEDSTAGE_IDLE };
573
574 // The time that the job stage was set.
575 // Used by the Greedy Algorithm to decide when to run JOB_ABORTED jobs again.
576 QDateTime stateTime;
577 QDateTime lastAbortTime;
578 QDateTime lastErrorTime;
579
580 StartupCondition fileStartupCondition { START_ASAP };
581 StartupCondition startupCondition { START_ASAP };
582 CompletionCondition completionCondition { FINISH_SEQUENCE };
583
584 int sequenceCount { 0 };
585 int completedCount { 0 };
586
587 QDateTime fileStartupTime;
588 QDateTime startupTime;
589 QDateTime completionTime;
590 // An alternative completionTime field used by the greedy scheduler algorithm.
591 QDateTime greedyCompletionTime;
592 // The reason this job is stopping/will-be stopped.
593 QString stopReason;
594
595 /* @internal Caches to optimize cell rendering. */
596 /* @{ */
597 double altitudeAtStartup { 0 };
598 double altitudeAtCompletion { 0 };
599 bool settingAtStartup { false };
600 bool settingAtCompletion { false };
601 /* @} */
602
603 QUrl sequenceFile;
604 QUrl fitsFile;
605
606 double minAltitude { UNDEFINED_ALTITUDE };
607 double minMoonSeparation { -1 };
608
609 bool enforceWeather { false };
610 bool enforceTwilight { false };
611 bool enforceArtificialHorizon { false };
612
613 QDateTime nextDawn;
614 QDateTime nextDusk;
615
616 StepPipeline stepPipeline { USE_NONE };
617
618 int64_t estimatedTime { -1 };
619 int64_t estimatedTimePerRepeat { 0 };
620 int64_t estimatedStartupTime { 0 };
621 int64_t estimatedTimeLeftThisRepeat { 0 };
622 uint16_t repeatsRequired { 1 };
623 uint16_t repeatsRemaining { 1 };
624 bool inSequenceFocus { false };
625 QString m_InitialFilter;
626
627 QString dateTimeDisplayFormat;
628
629 bool lightFramesRequired { false };
630 bool m_CalibrationMountPark {false};
631
632 QMap<QString, uint16_t> capturedFramesMap;
633
634 /// Pointer to Moon object
635 KSMoon *moon { nullptr };
636
637 // Used to display human-readable job progress.
638 QList<JobProgress> m_Progress;
639
640 // This class is used to cache the results computed in getNextPossibleStartTime()
641 // which is called repeatedly by the Greedy scheduler.
642 // The cache would need to be cleared if something changes that would affect the
643 // start time of jobs (geography, altitide constraints) so it is reset at the start
644 // of all schedule calculations--which does not impact its effectiveness.
645 class StartTimeCache
646 {
647 // Keep track of calls to getNextPossibleStartTime, storing the when, until and result.
648 struct StartTimeComputation
649 {
650 QDateTime from;
651 QDateTime until;
652 QDateTime result;
653 };
654 public:
655 StartTimeCache() {}
656 // Check if the computation has been done, and if so, return the previous result.
657 bool check(const QDateTime &from, const QDateTime &until,
658 QDateTime *result, QDateTime *newFrom) const;
659 // Add a result to the cache.
660 void add(const QDateTime &from, const QDateTime &until, const QDateTime &result) const;
661 // Clear the cache.
662 void clear() const;
663 private:
664 // Made this mutable and all methods const so that the cache could be
665 // used in SchedulerJob const methods.
666 mutable QList<StartTimeComputation> startComputations;
667 };
668 StartTimeCache startTimeCache;
669
670 // These are used in testing, instead of KStars::Instance() resources
671 static KStarsDateTime *storedLocalTime;
672 static GeoLocation *storedGeo;
673 static ArtificialHorizon *storedHorizon;
674};
675} // Ekos namespace
Represents custom area from the horizon upwards which represent blocked views from the vantage point ...
Contains all relevant information for specifying a location on Earth: City Name, State/Province name,...
Definition geolocation.h:28
Provides necessary information about the Moon.
Definition ksmoon.h:26
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
Sequence Job is a container for the details required to capture a series of images.
The sky coordinates of a point in the sky.
Definition skypoint.h:45
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
Type type(const QSqlDatabase &db)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:78
@ SCHEDJOB_IDLE
Job was just created, and is not evaluated yet.
QMap< QString, uint16_t > CapturedFramesMap
mapping signature --> frames count
KGuiItem properties()
void append(QList< T > &&value)
void clear()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.