7#include "schedulerjob.h"
10#include "artificialhorizoncomponent.h"
11#include "kstarsdata.h"
12#include "skymapcomposite.h"
15#include "schedulermodulestate.h"
16#include "schedulerutils.h"
20#include <knotification.h>
22#include <ekos_scheduler_debug.h>
24#define BAD_SCORE -1000
25#define MIN_ALTITUDE 15.0
33QString SchedulerJob::jobStatusString(SchedulerJobStatus state)
57QString SchedulerJob::jobStageString(SchedulerJobStage state)
63 case SCHEDSTAGE_SLEWING:
65 case SCHEDSTAGE_SLEW_COMPLETE:
66 return "SLEW_COMPLETE";
67 case SCHEDSTAGE_FOCUSING:
69 case SCHEDSTAGE_FOCUS_COMPLETE:
70 return "FOCUS_COMPLETE";
71 case SCHEDSTAGE_ALIGNING:
73 case SCHEDSTAGE_ALIGN_COMPLETE:
74 return "ALIGN_COMPLETE";
75 case SCHEDSTAGE_RESLEWING:
77 case SCHEDSTAGE_RESLEWING_COMPLETE:
78 return "RESLEWING_COMPLETE";
79 case SCHEDSTAGE_POSTALIGN_FOCUSING:
80 return "POSTALIGN_FOCUSING";
81 case SCHEDSTAGE_POSTALIGN_FOCUSING_COMPLETE:
82 return "POSTALIGN_FOCUSING_COMPLETE";
83 case SCHEDSTAGE_GUIDING:
85 case SCHEDSTAGE_GUIDING_COMPLETE:
86 return "GUIDING_COMPLETE";
87 case SCHEDSTAGE_CAPTURING:
89 case SCHEDSTAGE_COMPLETE:
95QString SchedulerJob::jobStartupConditionString(StartupCondition condition)
const
107QString SchedulerJob::jobCompletionConditionString(CompletionCondition condition)
const
111 case FINISH_SEQUENCE:
123SchedulerJob::SchedulerJob()
125 if (KStarsData::Instance() !=
nullptr)
130SchedulerJob::SchedulerJob(
KSMoon *moonPtr)
135void SchedulerJob::setName(
const QString &value)
140void SchedulerJob::setGroup(
const QString &value)
145void SchedulerJob::setCompletedIterations(
int value)
147 completedIterations = value;
148 if (completionCondition == FINISH_REPEAT)
149 setRepeatsRemaining(getRepeatsRequired() - completedIterations);
154 return Ekos::SchedulerModuleState::getLocalTime();
160 return storedHorizon;
161 if (KStarsData::Instance() ==
nullptr || KStarsData::Instance()->skyComposite() ==
nullptr
162 || KStarsData::Instance()->skyComposite()->artificialHorizon() ==
nullptr)
164 return &KStarsData::Instance()->
skyComposite()->artificialHorizon()->getHorizon();
167void SchedulerJob::setStartupCondition(
const StartupCondition &value)
169 startupCondition = value;
172 if (value == START_ASAP)
176 setEstimatedTime(estimatedTime);
179 SchedulerModuleState::calculateDawnDusk(startupTime, nextDawn, nextDusk);
182void SchedulerJob::setStartupTime(
const QDateTime &value)
188 startupCondition = START_AT;
190 startupCondition = fileStartupCondition;
193 altitudeAtStartup = SchedulerUtils::findAltitude(targetCoords, startupTime, &settingAtStartup);
196 setEstimatedTime(estimatedTime);
199 SchedulerModuleState::calculateDawnDusk(startupTime, nextDawn, nextDusk);
202void SchedulerJob::setSequenceFile(
const QUrl &value)
204 sequenceFile = value;
207void SchedulerJob::setFITSFile(
const QUrl &value)
212void SchedulerJob::setMinAltitude(
const double &value)
217bool SchedulerJob::hasAltitudeConstraint()
const
219 return hasMinAltitude() ||
220 (enforceArtificialHorizon && (getHorizon() !=
nullptr) && getHorizon()->altitudeConstraintsExist()) ||
221 (Options::enableAltitudeLimits() &&
222 (Options::minimumAltLimit() > 0 ||
223 Options::maximumAltLimit() < 90));
226void SchedulerJob::setMinMoonSeparation(
const double &value)
228 minMoonSeparation = value;
231void SchedulerJob::setEnforceWeather(
bool value)
233 enforceWeather = value;
236void SchedulerJob::setGreedyCompletionTime(
const QDateTime &value)
238 greedyCompletionTime = value;
241void SchedulerJob::setCompletionTime(
const QDateTime &value)
248 setCompletionCondition(FINISH_AT);
249 completionTime = value;
250 altitudeAtCompletion = SchedulerUtils::findAltitude(targetCoords, completionTime, &settingAtCompletion);
251 setEstimatedTime(-1);
254 else if (FINISH_LOOP == completionCondition)
257 altitudeAtCompletion = SchedulerUtils::findAltitude(targetCoords, completionTime, &settingAtCompletion);
258 setEstimatedTime(-1);
261 else if (startupTime.
isValid())
263 completionTime = startupTime.
addSecs(estimatedTime);
264 altitudeAtCompletion = SchedulerUtils::findAltitude(targetCoords, completionTime, &settingAtCompletion);
267 else setEstimatedTime(estimatedTime);
271 Q_ASSERT_X(completionTime.
isValid() ?
272 (FINISH_AT == completionCondition || FINISH_REPEAT == completionCondition || FINISH_SEQUENCE == completionCondition) :
273 FINISH_LOOP == completionCondition,
274 __FUNCTION__,
"Valid completion time implies job is FINISH_AT/REPEAT/SEQUENCE, else job is FINISH_LOOP.");
277void SchedulerJob::setCompletionCondition(
const CompletionCondition &value)
279 completionCondition = value;
282 switch (completionCondition)
288 if (0 < getRepeatsRequired())
289 setRepeatsRequired(0);
292 case FINISH_SEQUENCE:
293 if (1 != getRepeatsRequired())
294 setRepeatsRequired(1);
298 if (0 == getRepeatsRequired())
299 setRepeatsRequired(1);
307void SchedulerJob::setStepPipeline(
const StepPipeline &value)
309 stepPipeline = value;
312void SchedulerJob::setState(
const SchedulerJobStatus &value)
315 stateTime = getLocalTime();
320 lastErrorTime = getLocalTime();
327 setStartupCondition(fileStartupCondition);
328 setStartupTime(fileStartupTime);
329 setEstimatedTime(-1);
335 lastAbortTime = getLocalTime();
336 setStartupCondition(fileStartupCondition);
342void SchedulerJob::setSequenceCount(
const int count)
344 sequenceCount = count;
347void SchedulerJob::setCompletedCount(
const int count)
349 completedCount = count;
353void SchedulerJob::setStage(
const SchedulerJobStage &value)
358void SchedulerJob::setFileStartupCondition(
const StartupCondition &value)
360 fileStartupCondition = value;
363void SchedulerJob::setFileStartupTime(
const QDateTime &value)
365 fileStartupTime = value;
368void SchedulerJob::setEstimatedTime(
const int64_t &value)
379 if (START_ASAP != fileStartupCondition && FINISH_AT == completionCondition)
381 estimatedTime = startupTime.
secsTo(completionTime);
384 else if (FINISH_AT != completionCondition && FINISH_LOOP != completionCondition)
386 estimatedTime = value;
387 completionTime = startupTime.
addSecs(value);
388 altitudeAtCompletion = SchedulerUtils::findAltitude(targetCoords, completionTime, &settingAtCompletion);
391 else estimatedTime = value;
394void SchedulerJob::setInSequenceFocus(
bool value)
396 inSequenceFocus = value;
399void SchedulerJob::setEnforceTwilight(
bool value)
401 enforceTwilight = value;
402 SchedulerModuleState::calculateDawnDusk(startupTime, nextDawn, nextDusk);
405void SchedulerJob::setEnforceArtificialHorizon(
bool value)
407 enforceArtificialHorizon = value;
410void SchedulerJob::setLightFramesRequired(
bool value)
412 lightFramesRequired = value;
415void SchedulerJob::setCalibrationMountPark(
bool value)
417 m_CalibrationMountPark = value;
420void SchedulerJob::setRepeatsRequired(
const uint16_t &value)
422 repeatsRequired = value;
425 if (1 < repeatsRequired)
427 if (FINISH_REPEAT != completionCondition)
428 setCompletionCondition(FINISH_REPEAT);
430 else if (0 < repeatsRequired)
432 if (FINISH_SEQUENCE != completionCondition)
433 setCompletionCondition(FINISH_SEQUENCE);
437 if (FINISH_LOOP != completionCondition)
438 setCompletionCondition(FINISH_LOOP);
442void SchedulerJob::setRepeatsRemaining(
const uint16_t &value)
444 repeatsRemaining = value;
449 capturedFramesMap = value;
452void SchedulerJob::setTargetCoords(
const dms &ra,
const dms &dec,
double djd)
457 targetCoords.
apparentCoord(
static_cast<long double>(J2000), djd);
460void SchedulerJob::setPositionAngle(
double value)
462 m_PositionAngle = value;
465void SchedulerJob::reset()
468 stage = SCHEDSTAGE_IDLE;
469 stateTime = getLocalTime();
473 startupCondition = fileStartupCondition;
474 startupTime = fileStartupCondition == START_AT ? fileStartupTime :
QDateTime();
477 SchedulerModuleState::calculateDawnDusk(startupTime, nextDawn, nextDusk);
483 repeatsRemaining = repeatsRequired;
484 completedIterations = 0;
490bool SchedulerJob::decreasingAltitudeOrder(SchedulerJob
const *job1, SchedulerJob
const *job2,
QDateTime const &when)
492 bool A_is_setting = job1->settingAtStartup;
493 double const altA = when.
isValid() ?
494 SchedulerUtils::findAltitude(job1->getTargetCoords(), when, &A_is_setting) :
495 job1->altitudeAtStartup;
497 bool B_is_setting = job2->settingAtStartup;
498 double const altB = when.
isValid() ?
499 SchedulerUtils::findAltitude(job2->getTargetCoords(), when, &B_is_setting) :
500 job2->altitudeAtStartup;
503 if (A_is_setting && !B_is_setting)
505 else if (!A_is_setting && B_is_setting)
509 return (A_is_setting && B_is_setting) ? altA < altB : altB < altA;
512bool SchedulerJob::satisfiesAltitudeConstraint(
double azimuth,
double altitude,
QString *altitudeReason)
const
515 if (Options::enableAltitudeLimits() &&
516 (altitude < Options::minimumAltLimit() ||
517 altitude > Options::maximumAltLimit()))
519 if (altitudeReason !=
nullptr)
521 if (altitude < Options::minimumAltLimit())
522 *altitudeReason =
QString(
"altitude %1 < mount altitude limit %2")
523 .
arg(altitude, 0,
'f', 1).
arg(Options::minimumAltLimit(), 0,
'f', 1);
525 *altitudeReason =
QString(
"altitude %1 > mount altitude limit %2")
526 .
arg(altitude, 0,
'f', 1).
arg(Options::maximumAltLimit(), 0,
'f', 1);
531 if (altitude < getMinAltitude())
533 if (altitudeReason !=
nullptr)
534 *altitudeReason =
QString(
"altitude %1 < minAltitude %2").
arg(altitude, 0,
'f', 1).
arg(getMinAltitude(), 0,
'f', 1);
538 if (getHorizon() !=
nullptr && enforceArtificialHorizon)
539 return getHorizon()->isAltitudeOK(azimuth, altitude, altitudeReason);
544bool SchedulerJob::moonSeparationOK(
QDateTime const &when)
const
546 if (moon ==
nullptr)
return true;
554 SkyPoint const target = getTargetCoords();
563 CachingDms LST = SchedulerModuleState::getGeo()->GSTtoLST(SchedulerModuleState::getGeo()->LTtoUT(ltWhen).gst());
564 moon->
updateCoords(&numbers,
true, SchedulerModuleState::getGeo()->lat(), &LST,
true);
568 return (separation >= getMinMoonSeparation());
571QDateTime SchedulerJob::calculateNextTime(
QDateTime const &when,
bool checkIfConstraintsAreMet,
int increment,
582 SkyPoint const target = getTargetCoords();
588 KStarsDateTime const ut = SchedulerModuleState::getGeo()->LTtoUT(ltWhen);
590 auto maxMinute = 1e8;
591 if (!runningJob && until.
isValid())
592 maxMinute = when.
secsTo(until) / 60;
594 if (maxMinute > 24 * 60)
598 for (
unsigned int minute = 0; minute < maxMinute; minute += increment)
604 if (getEnforceTwilight() && !runsDuringAstronomicalNightTime(ltOffset, &nextSuccess))
606 if (checkIfConstraintsAreMet)
611 const int minutesToSuccess = ltOffset.
secsTo(nextSuccess) / 60 - increment;
612 if (minutesToSuccess > 0)
613 minute += minutesToSuccess;
619 if (reason) *reason =
"twilight";
629 CachingDms const LST = SchedulerModuleState::getGeo()->GSTtoLST(SchedulerModuleState::getGeo()->LTtoUT(ltOffset).gst());
634 bool const altitudeOK = satisfiesAltitudeConstraint(azimuth, altitude, reason);
640 if (0 < getMinMoonSeparation() && !moonSeparationOK(ltOffset))
642 if (checkIfConstraintsAreMet)
646 if (reason) *reason =
QString(
"moon separation");
651 if (checkIfConstraintsAreMet)
654 else if (!checkIfConstraintsAreMet)
661bool SchedulerJob::runsDuringAstronomicalNightTime(
const QDateTime &time,
669 static QDateTime previousMinDawnDusk, previousTime;
671 static bool previousAnswer;
672 static double previousPreDawnTime = 0;
676 static std::mutex nightTimeMutex;
677 const std::lock_guard<std::mutex> lock(nightTimeMutex);
681 time >= previousTime && time < previousMinDawnDusk &&
682 SchedulerModuleState::getGeo() == previousGeo &&
683 Options::preDawnTime() == previousPreDawnTime)
685 if (!previousAnswer && nextPossibleSuccess !=
nullptr)
686 *nextPossibleSuccess = nextSuccess;
687 return previousAnswer;
691 previousAnswer = runsDuringAstronomicalNightTimeInternal(time, &previousMinDawnDusk, &nextSuccess);
693 previousGeo = SchedulerModuleState::getGeo();
694 previousPreDawnTime = Options::preDawnTime();
695 if (!previousAnswer && nextPossibleSuccess !=
nullptr)
696 *nextPossibleSuccess = nextSuccess;
697 return previousAnswer;
702bool SchedulerJob::runsDuringAstronomicalNightTimeInternal(
const QDateTime &time,
QDateTime *minDawnDusk,
706 QDateTime nDawn = nextDawn, nDusk = nextDusk;
710 SchedulerModuleState::calculateDawnDusk(time, nDawn, nDusk);
719 QDateTime const earlyDawn = nDawn.
addSecs(-60.0 * abs(Options::preDawnTime()));
721 *minDawnDusk = earlyDawn < nDusk ? earlyDawn : nDusk;
725 bool result = nDawn < nDusk && t <= earlyDawn;
728 if (nextPossibleSuccess !=
nullptr)
730 if (result) *nextPossibleSuccess =
QDateTime();
731 else *nextPossibleSuccess = nDusk;
737void SchedulerJob::setInitialFilter(
const QString &value)
739 m_InitialFilter = value;
742const QString &SchedulerJob::getInitialFilter()
const
744 return m_InitialFilter;
747bool SchedulerJob::StartTimeCache::check(
const QDateTime &from,
const QDateTime &until,
753 foreach (
const StartTimeComputation &computation, startComputations)
755 if (from >= computation.from &&
756 (!computation.until.isValid() || from < computation.until) &&
757 (!computation.result.isValid() || from < computation.result))
759 if (computation.result.isValid() || until <= computation.until)
762 *result = computation.result;
770 *newFrom = computation.until;
778void SchedulerJob::StartTimeCache::clear()
const
780 startComputations.clear();
786 if (startComputations.size() > 10)
787 startComputations.clear();
792 endTime = from.
addSecs(24 * 3600);
802 StartTimeComputation c;
806 startComputations.push_back(c);
810QDateTime SchedulerJob::getNextPossibleStartTime(
const QDateTime &when,
int increment,
bool runningJob,
820 if (!runningJob && START_AT == getFileStartupCondition())
822 int secondsFromNow = ltWhen.
secsTo(getFileStartupTime());
823 if (secondsFromNow < -500)
826 ltWhen = secondsFromNow > 0 ? getFileStartupTime() : ltWhen;
830 if (getCompletionCondition() == FINISH_AT)
832 const QDateTime &t = getCompletionTime();
838 return calculateNextTime(ltWhen,
true, increment,
nullptr, runningJob, until);
842 if (startTimeCache.check(ltWhen, until, &result, &newFrom))
849 result = calculateNextTime(ltWhen,
true, increment,
nullptr, runningJob, until);
850 startTimeCache.add(ltWhen, until, result);
865 if (START_AT == getFileStartupCondition())
867 if (getFileStartupTime().secsTo(ltStart) < -120)
871 if (reason) *reason =
"before start-at time";
878 if (getCompletionCondition() == FINISH_AT)
880 const QDateTime &t = getCompletionTime();
881 if (t.
isValid() && t < ltStart)
883 if (reason) *reason =
"end-at time";
886 auto result = calculateNextTime(ltStart,
false, increment, reason,
false, until);
887 if (!result.
isValid() || result.
secsTo(getCompletionTime()) < 0)
889 if (reason) *reason =
"end-at time";
890 return getCompletionTime();
895 return calculateNextTime(ltStart,
false, increment, reason,
false, until);
905 auto exposure =
properties[SequenceJob::SJ_Exposure].toDouble();
908 int precisionRequired = 0;
909 double fraction = exposure - fabs(exposure);
910 if (fraction > .0001)
912 precisionRequired = 1;
913 fraction = fraction * 10;
914 fraction = fraction - fabs(fraction);
915 if (fraction > .0001)
917 precisionRequired = 2;
918 fraction = fraction * 10;
919 fraction = fraction - fabs(fraction);
920 if (fraction > .0001)
921 precisionRequired = 3;
924 if (precisionRequired == 0)
929 if (
properties.contains(SequenceJob::SJ_Filter))
931 auto filterType =
properties[SequenceJob::SJ_Filter].toString();
941 else if (frameType != FRAME_LIGHT)
950QString progressLine(
const SchedulerJob::JobProgress &progress)
952 QString label = progressLineLabel(progress.type, progress.properties, progress.isDarkFlat).
append(
":");
954 const double seconds = progress.numCompleted * progress.properties[SequenceJob::SJ_Exposure].
toDouble();
958 else if (seconds < 60)
960 else if (seconds < 60 * 60)
968 .
arg(label, -12,
' ')
969 .
arg(progress.numCompleted, 4)
975const QString SchedulerJob::getProgressSummary()
const
978 for (
const auto &p : m_Progress)
980 summary.
append(progressLine(p));
988 bool is_setting =
false;
989 double const alt = SchedulerUtils::findAltitude(getTargetCoords(),
QDateTime(), &is_setting);
994 {
"pa", m_PositionAngle},
995 {
"targetRA", targetCoords.
ra0().
Hours()},
999 {
"sequenceCount", sequenceCount},
1000 {
"completedCount", completedCount},
1001 {
"minAltitude", minAltitude},
1002 {
"minMoonSeparation", minMoonSeparation},
1003 {
"repeatsRequired", repeatsRequired},
1004 {
"repeatsRemaining", repeatsRemaining},
1005 {
"inSequenceFocus", inSequenceFocus},
1006 {
"startupTime", startupTime.
isValid() ? startupTime.
toString() :
"--"},
1007 {
"completionTime", completionTime.
isValid() ? completionTime.
toString() :
"--"},
1009 {
"sequence", sequenceFile.
toString() },
Represents custom area from the horizon upwards which represent blocked views from the vantage point ...
a dms subclass that caches its sine and cosine values every time the angle is changed.
Contains all relevant information for specifying a location on Earth: City Name, State/Province name,...
static KNotification * event(const QString &eventId, const QString &text=QString(), const QPixmap &pixmap=QPixmap(), const NotificationFlags &flags=CloseOnTimeout, const QString &componentName=QString())
Provides necessary information about the Moon.
There are several time-dependent values used in position calculations, that are not specific to an ob...
void updateCoords(const KSNumbers *num, bool includePlanets=true, const CachingDms *lat=nullptr, const CachingDms *LST=nullptr, bool forceRecompute=false) override
Update position of the planet (reimplemented from SkyPoint)
SkyMapComposite * skyComposite()
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
SkyObject * findByName(const QString &name, bool exact=true) override
Search the children of this SkyMapComposite for a SkyObject whose name matches the argument.
Provides all necessary information about an object in the sky: its coordinates, name(s),...
The sky coordinates of a point in the sky.
void apparentCoord(long double jd0, long double jdf)
Computes the apparent coordinates for this SkyPoint for any epoch, accounting for the effects of prec...
const CachingDms & ra0() const
virtual void updateCoordsNow(const KSNumbers *num)
updateCoordsNow Shortcut for updateCoords( const KSNumbers *num, false, nullptr, nullptr,...
dms angularDistanceTo(const SkyPoint *sp, double *const positionAngle=nullptr) const
Computes the angular distance between two SkyObjects.
void EquatorialToHorizontal(const CachingDms *LST, const CachingDms *lat)
Determine the (Altitude, Azimuth) coordinates of the SkyPoint from its (RA, Dec) coordinates,...
void setRA0(dms r)
Sets RA0, the catalog Right Ascension.
const CachingDms & dec0() const
void setDec0(dms d)
Sets Dec0, the catalog Declination.
An angle, stored as degrees, but expressible in many ways.
const double & Degrees() const
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
@ SCHEDJOB_ABORTED
Job encountered a transitory issue while processing, and will be rescheduled.
@ SCHEDJOB_INVALID
Job has an incorrect configuration, and cannot proceed.
@ SCHEDJOB_ERROR
Job encountered a fatal issue while processing, and must be reset manually.
@ SCHEDJOB_COMPLETE
Job finished all required captures.
@ SCHEDJOB_EVALUATION
Job is being evaluated.
@ SCHEDJOB_SCHEDULED
Job was evaluated, and has a schedule.
@ SCHEDJOB_BUSY
Job is being processed.
@ SCHEDJOB_IDLE
Job was just created, and is not evaluated yet.
QMap< QString, uint16_t > CapturedFramesMap
mapping signature --> frames count
QString label(StandardShortcut id)
QDateTime addSecs(qint64 s) const const
bool isValid() const const
qint64 secsTo(const QDateTime &other) const const
Qt::TimeSpec timeSpec() const const
QString toString(QStringView format, QCalendar cal) const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
qsizetype size() const const
double toDouble(bool *ok) const const
QString toString(FormattingOptions options) const const