7#include "schedulerjob.h"
10#include "artificialhorizoncomponent.h"
11#include "kstarsdata.h"
12#include "skymapcomposite.h"
15#include "schedulermodulestate.h"
16#include "schedulerutils.h"
19#include <knotification.h>
21#include <ekos_scheduler_debug.h>
23#define BAD_SCORE -1000
24#define MIN_ALTITUDE 15.0
32QString SchedulerJob::jobStatusString(SchedulerJobStatus state)
56QString SchedulerJob::jobStageString(SchedulerJobStage state)
62 case SCHEDSTAGE_SLEWING:
64 case SCHEDSTAGE_SLEW_COMPLETE:
65 return "SLEW_COMPLETE";
66 case SCHEDSTAGE_FOCUSING:
68 case SCHEDSTAGE_FOCUS_COMPLETE:
69 return "FOCUS_COMPLETE";
70 case SCHEDSTAGE_ALIGNING:
72 case SCHEDSTAGE_ALIGN_COMPLETE:
73 return "ALIGN_COMPLETE";
74 case SCHEDSTAGE_RESLEWING:
76 case SCHEDSTAGE_RESLEWING_COMPLETE:
77 return "RESLEWING_COMPLETE";
78 case SCHEDSTAGE_POSTALIGN_FOCUSING:
79 return "POSTALIGN_FOCUSING";
80 case SCHEDSTAGE_POSTALIGN_FOCUSING_COMPLETE:
81 return "POSTALIGN_FOCUSING_COMPLETE";
82 case SCHEDSTAGE_GUIDING:
84 case SCHEDSTAGE_GUIDING_COMPLETE:
85 return "GUIDING_COMPLETE";
86 case SCHEDSTAGE_CAPTURING:
88 case SCHEDSTAGE_COMPLETE:
94QString SchedulerJob::jobStartupConditionString(StartupCondition condition)
const
106QString SchedulerJob::jobCompletionConditionString(CompletionCondition condition)
const
110 case FINISH_SEQUENCE:
122SchedulerJob::SchedulerJob()
124 if (KStarsData::Instance() !=
nullptr)
129SchedulerJob::SchedulerJob(
KSMoon *moonPtr)
134void SchedulerJob::setName(
const QString &value)
139void SchedulerJob::setGroup(
const QString &value)
146void SchedulerJob::setCompletedIterations(
int value)
148 completedIterations = value;
149 if (completionCondition == FINISH_REPEAT)
150 setRepeatsRemaining(getRepeatsRequired() - completedIterations);
155 return Ekos::SchedulerModuleState::getLocalTime();
161 return storedHorizon;
162 if (KStarsData::Instance() ==
nullptr || KStarsData::Instance()->skyComposite() ==
nullptr
163 || KStarsData::Instance()->skyComposite()->artificialHorizon() ==
nullptr)
165 return &KStarsData::Instance()->
skyComposite()->artificialHorizon()->getHorizon();
168void SchedulerJob::setStartupCondition(
const StartupCondition &value)
170 startupCondition = value;
173 if (value == START_ASAP)
177 setEstimatedTime(estimatedTime);
180 SchedulerModuleState::calculateDawnDusk(startupTime, nextDawn, nextDusk);
183void SchedulerJob::setStartupTime(
const QDateTime &value,
bool refreshDawnDusk)
189 startupCondition = START_AT;
191 startupCondition = fileStartupCondition;
194 altitudeAtStartup = SchedulerUtils::findAltitude(getTargetCoords(), startupTime, &settingAtStartup);
197 setEstimatedTime(estimatedTime);
200 for (
auto follower : followerJobs())
201 follower->setStartupTime(value,
false);
205 SchedulerModuleState::calculateDawnDusk(startupTime, nextDawn, nextDusk);
208void SchedulerJob::setSequenceFile(
const QUrl &value)
210 sequenceFile = value;
213void SchedulerJob::setFITSFile(
const QUrl &value)
218void SchedulerJob::setMinAltitude(
const double &value)
223bool SchedulerJob::hasAltitudeConstraint()
const
225 return hasMinAltitude() ||
226 (getEnforceArtificialHorizon() && (getHorizon() !=
nullptr) && getHorizon()->altitudeConstraintsExist()) ||
227 (Options::enableAltitudeLimits() &&
228 (Options::minimumAltLimit() > 0 ||
229 Options::maximumAltLimit() < 90));
232void SchedulerJob::setMinMoonSeparation(
const double &value)
234 minMoonSeparation = value;
237void SchedulerJob::setEnforceWeather(
bool value)
239 enforceWeather = value;
242void SchedulerJob::setStopTime(
const QDateTime &value)
249 altitudeAtStop = SchedulerUtils::findAltitude(getTargetCoords(), stopTime, &settingAtStop);
252 for (
auto follower : followerJobs())
255 if (follower->getStartupTime().isValid() && value.
isValid()
256 && (follower->getEstimatedTime() < 0 || follower->getEstimatedTime() > getEstimatedTime()))
257 follower->setEstimatedTime(getEstimatedTime());
262void SchedulerJob::setFinishAtTime(
const QDateTime &value)
269 setCompletionCondition(FINISH_AT);
270 finishAtTime = value;
271 setEstimatedTime(-1);
274 else if (FINISH_LOOP == completionCondition)
277 setEstimatedTime(-1);
280 else if (startupTime.
isValid())
282 finishAtTime = startupTime.
addSecs(estimatedTime);
285 else setEstimatedTime(estimatedTime);
289 Q_ASSERT_X(finishAtTime.
isValid() ?
290 (FINISH_AT == completionCondition || FINISH_REPEAT == completionCondition || FINISH_SEQUENCE == completionCondition) :
291 FINISH_LOOP == completionCondition,
292 __FUNCTION__,
"Valid completion time implies job is FINISH_AT/REPEAT/SEQUENCE, else job is FINISH_LOOP.");
295void SchedulerJob::setCompletionCondition(
const CompletionCondition &value)
297 completionCondition = value;
300 switch (completionCondition)
306 if (0 < getRepeatsRequired())
307 setRepeatsRequired(0);
310 case FINISH_SEQUENCE:
311 if (1 != getRepeatsRequired())
312 setRepeatsRequired(1);
316 if (0 == getRepeatsRequired())
317 setRepeatsRequired(1);
325void SchedulerJob::setStepPipeline(
const StepPipeline &value)
327 stepPipeline = value;
330void SchedulerJob::setState(
const SchedulerJobStatus &value,
bool force)
333 stateTime = getLocalTime();
339 lastErrorTime = getLocalTime();
345 setStartupCondition(fileStartupCondition);
346 setStartupTime(startAtTime);
347 setEstimatedTime(-1);
351 lastAbortTime = getLocalTime();
352 setStartupCondition(fileStartupCondition);
361 setFollowerState(value, force);
364void SchedulerJob::setFollowerState(
const SchedulerJobStatus &value,
bool force)
366 for (
auto follower : followerJobs())
377 follower->setState(value);
381 follower->setState(follower->getCompletionCondition() == FINISH_LOOP
387 follower->setState(value);
393void SchedulerJob::updateSharedFollowerAttributes()
396 for (
auto follower : followerJobs())
398 follower->setStartupTime(getStartupTime(),
false);
399 follower->setStartAtTime(getStartAtTime());
400 follower->setFollowerState(getState(),
true);
405void SchedulerJob::setSequenceCount(
const int count)
407 sequenceCount = count;
410void SchedulerJob::setCompletedCount(
const int count)
412 completedCount = count;
416void SchedulerJob::setStage(
const SchedulerJobStage &value)
421void SchedulerJob::setFileStartupCondition(
const StartupCondition &value)
423 fileStartupCondition = value;
426void SchedulerJob::setStartAtTime(
const QDateTime &value)
431void SchedulerJob::setEstimatedTime(
const int64_t &value)
442 if (START_ASAP != fileStartupCondition && FINISH_AT == completionCondition)
444 estimatedTime = startupTime.
secsTo(finishAtTime);
447 else if (FINISH_AT != completionCondition && FINISH_LOOP != completionCondition)
449 estimatedTime = value;
452 else estimatedTime = value;
455void SchedulerJob::setInSequenceFocus(
bool value)
457 inSequenceFocus = value;
460void SchedulerJob::setEnforceTwilight(
bool value)
462 enforceTwilight = value;
463 SchedulerModuleState::calculateDawnDusk(startupTime, nextDawn, nextDusk);
466void SchedulerJob::setEnforceArtificialHorizon(
bool value)
468 enforceArtificialHorizon = value;
471void SchedulerJob::setLightFramesRequired(
bool value)
473 lightFramesRequired = value;
476void SchedulerJob::setCalibrationMountPark(
bool value)
478 m_CalibrationMountPark = value;
481void SchedulerJob::setRepeatsRequired(
const uint16_t &value)
483 repeatsRequired = value;
486 if (1 < repeatsRequired)
488 if (FINISH_REPEAT != completionCondition)
489 setCompletionCondition(FINISH_REPEAT);
491 else if (0 < repeatsRequired)
493 if (FINISH_SEQUENCE != completionCondition)
494 setCompletionCondition(FINISH_SEQUENCE);
498 if (FINISH_LOOP != completionCondition)
499 setCompletionCondition(FINISH_LOOP);
503void SchedulerJob::setRepeatsRemaining(
const uint16_t &value)
505 repeatsRemaining = value;
510 capturedFramesMap = value;
513void SchedulerJob::setTargetCoords(
const dms &ra,
const dms &dec,
double djd)
518 targetCoords.
apparentCoord(
static_cast<long double>(J2000), djd);
521void SchedulerJob::setPositionAngle(
double value)
523 m_PositionAngle = value;
526void SchedulerJob::reset()
529 stage = SCHEDSTAGE_IDLE;
530 stateTime = getLocalTime();
534 startupCondition = fileStartupCondition;
535 startupTime = fileStartupCondition == START_AT ? startAtTime :
QDateTime();
538 SchedulerModuleState::calculateDawnDusk(startupTime, nextDawn, nextDusk);
544 repeatsRemaining = repeatsRequired;
545 completedIterations = 0;
551bool SchedulerJob::decreasingAltitudeOrder(SchedulerJob
const *job1, SchedulerJob
const *job2,
QDateTime const &when)
553 bool A_is_setting = job1->settingAtStartup;
554 double const altA = when.
isValid() ?
555 SchedulerUtils::findAltitude(job1->getTargetCoords(), when, &A_is_setting) :
556 job1->altitudeAtStartup;
558 bool B_is_setting = job2->settingAtStartup;
559 double const altB = when.
isValid() ?
560 SchedulerUtils::findAltitude(job2->getTargetCoords(), when, &B_is_setting) :
561 job2->altitudeAtStartup;
564 if (A_is_setting && !B_is_setting)
566 else if (!A_is_setting && B_is_setting)
570 return (A_is_setting && B_is_setting) ? altA < altB : altB < altA;
573bool SchedulerJob::satisfiesAltitudeConstraint(
double azimuth,
double altitude,
QString *altitudeReason)
const
575 if (m_LeadJob !=
nullptr)
576 return m_LeadJob->satisfiesAltitudeConstraint(azimuth, altitude, altitudeReason);
579 if (Options::enableAltitudeLimits() &&
580 (altitude < Options::minimumAltLimit() ||
581 altitude > Options::maximumAltLimit()))
583 if (altitudeReason !=
nullptr)
585 if (altitude < Options::minimumAltLimit())
586 *altitudeReason =
QString(
"altitude %1 < mount altitude limit %2")
587 .
arg(altitude, 0,
'f', 1).
arg(Options::minimumAltLimit(), 0,
'f', 1);
589 *altitudeReason =
QString(
"altitude %1 > mount altitude limit %2")
590 .
arg(altitude, 0,
'f', 1).
arg(Options::maximumAltLimit(), 0,
'f', 1);
595 if (altitude < getMinAltitude())
597 if (altitudeReason !=
nullptr)
598 *altitudeReason =
QString(
"altitude %1 < minAltitude %2").
arg(altitude, 0,
'f', 1).
arg(getMinAltitude(), 0,
'f', 1);
602 if (getHorizon() !=
nullptr && enforceArtificialHorizon)
603 return getHorizon()->isAltitudeOK(azimuth, altitude, altitudeReason);
608bool SchedulerJob::moonSeparationOK(
QDateTime const &when)
const
610 if (moon ==
nullptr)
return true;
618 SkyPoint const target = getTargetCoords();
627 CachingDms LST = SchedulerModuleState::getGeo()->GSTtoLST(SchedulerModuleState::getGeo()->LTtoUT(ltWhen).gst());
628 moon->
updateCoords(&numbers,
true, SchedulerModuleState::getGeo()->lat(), &LST,
true);
632 return (separation >= getMinMoonSeparation());
635QDateTime SchedulerJob::calculateNextTime(
QDateTime const &when,
bool checkIfConstraintsAreMet,
int increment,
646 SkyPoint const target = getTargetCoords();
652 KStarsDateTime const ut = SchedulerModuleState::getGeo()->LTtoUT(ltWhen);
654 auto maxMinute = 1e8;
655 if (!runningJob && until.
isValid())
656 maxMinute = when.
secsTo(until) / 60;
658 if (maxMinute > 24 * 60)
662 for (
unsigned int minute = 0; minute < maxMinute; minute += increment)
668 if (getEnforceTwilight() && !runsDuringAstronomicalNightTime(ltOffset, &nextSuccess))
670 if (checkIfConstraintsAreMet)
675 const int minutesToSuccess = ltOffset.secsTo(nextSuccess) / 60 - increment;
676 if (minutesToSuccess > 0)
677 minute += minutesToSuccess;
683 if (reason) *reason =
"twilight";
693 CachingDms const LST = SchedulerModuleState::getGeo()->GSTtoLST(SchedulerModuleState::getGeo()->LTtoUT(ltOffset).gst());
698 bool const altitudeOK = satisfiesAltitudeConstraint(azimuth, altitude, reason);
704 if (0 < getMinMoonSeparation() && !moonSeparationOK(ltOffset))
706 if (checkIfConstraintsAreMet)
710 if (reason) *reason =
QString(
"moon separation");
715 if (checkIfConstraintsAreMet)
718 else if (!checkIfConstraintsAreMet)
725bool SchedulerJob::runsDuringAstronomicalNightTime(
const QDateTime &time,
728 if (m_LeadJob !=
nullptr)
729 return m_LeadJob->runsDuringAstronomicalNightTime(time, nextPossibleSuccess);
736 static QDateTime previousMinDawnDusk, previousTime;
738 static bool previousAnswer;
739 static double previousPreDawnTime = 0;
743 static std::mutex nightTimeMutex;
744 const std::lock_guard<std::mutex> lock(nightTimeMutex);
748 time >= previousTime && time < previousMinDawnDusk &&
749 SchedulerModuleState::getGeo() == previousGeo &&
750 Options::preDawnTime() == previousPreDawnTime)
752 if (!previousAnswer && nextPossibleSuccess !=
nullptr)
753 *nextPossibleSuccess = nextSuccess;
754 return previousAnswer;
758 previousAnswer = runsDuringAstronomicalNightTimeInternal(time, &previousMinDawnDusk, &nextSuccess);
760 previousGeo = SchedulerModuleState::getGeo();
761 previousPreDawnTime = Options::preDawnTime();
762 if (!previousAnswer && nextPossibleSuccess !=
nullptr)
763 *nextPossibleSuccess = nextSuccess;
764 return previousAnswer;
769bool SchedulerJob::runsDuringAstronomicalNightTimeInternal(
const QDateTime &time,
QDateTime *minDawnDusk,
772 if (m_LeadJob !=
nullptr)
773 return m_LeadJob->runsDuringAstronomicalNightTimeInternal(time, minDawnDusk, nextPossibleSuccess);
776 QDateTime nDawn = nextDawn, nDusk = nextDusk;
780 SchedulerModuleState::calculateDawnDusk(time, nDawn, nDusk);
789 QDateTime const earlyDawn = nDawn.
addSecs(-60.0 * abs(Options::preDawnTime()));
791 *minDawnDusk = earlyDawn < nDusk ? earlyDawn : nDusk;
795 bool result = nDawn < nDusk && t <= earlyDawn;
798 if (nextPossibleSuccess !=
nullptr)
800 if (result) *nextPossibleSuccess =
QDateTime();
801 else *nextPossibleSuccess = nDusk;
807void SchedulerJob::setInitialFilter(
const QString &value)
809 m_InitialFilter = value;
812const QString &SchedulerJob::getInitialFilter()
const
814 return m_InitialFilter;
817bool SchedulerJob::StartTimeCache::check(
const QDateTime &from,
const QDateTime &until,
823 foreach (
const StartTimeComputation &computation, startComputations)
825 if (from >= computation.from &&
826 (!computation.until.isValid() || from < computation.until) &&
827 (!computation.result.isValid() || from < computation.result))
829 if (computation.result.isValid() || until <= computation.until)
832 *result = computation.result;
840 *newFrom = computation.until;
848void SchedulerJob::StartTimeCache::clear()
const
850 startComputations.
clear();
856 if (startComputations.size() > 10)
857 startComputations.clear();
862 endTime = from.
addSecs(24 * 3600);
872 StartTimeComputation c;
876 startComputations.push_back(c);
880QDateTime SchedulerJob::getNextPossibleStartTime(
const QDateTime &when,
int increment,
bool runningJob,
890 if (!runningJob && START_AT == getFileStartupCondition())
892 int secondsFromNow = ltWhen.secsTo(getStartAtTime());
893 if (secondsFromNow < -500)
896 ltWhen = secondsFromNow > 0 ? getStartAtTime() : ltWhen;
900 if (getCompletionCondition() == FINISH_AT)
908 return calculateNextTime(ltWhen,
true, increment,
nullptr, runningJob, until);
912 if (startTimeCache.check(ltWhen, until, &result, &newFrom))
919 result = calculateNextTime(ltWhen,
true, increment,
nullptr, runningJob, until);
920 startTimeCache.add(ltWhen, until, result);
935 if (START_AT == getFileStartupCondition())
937 if (getStartAtTime().secsTo(ltStart) < -120)
941 if (reason) *reason =
"before start-at time";
948 if (getCompletionCondition() == FINISH_AT)
951 if (t.
isValid() && t < ltStart)
953 if (reason) *reason =
"end-at time";
956 auto result = calculateNextTime(ltStart,
false, increment, reason,
false, until);
957 if (!result.
isValid() || result.
secsTo(getFinishAtTime()) < 0)
959 if (reason) *reason =
"end-at time";
960 return getFinishAtTime();
965 return calculateNextTime(ltStart,
false, increment, reason,
false, until);
975 auto exposure =
properties[SequenceJob::SJ_Exposure].toDouble();
978 int precisionRequired = 0;
979 double fraction = exposure - fabs(exposure);
980 if (fraction > .0001)
982 precisionRequired = 1;
983 fraction = fraction * 10;
984 fraction = fraction - fabs(fraction);
985 if (fraction > .0001)
987 precisionRequired = 2;
988 fraction = fraction * 10;
989 fraction = fraction - fabs(fraction);
990 if (fraction > .0001)
991 precisionRequired = 3;
994 if (precisionRequired == 0)
999 if (
properties.contains(SequenceJob::SJ_Filter))
1001 auto filterType =
properties[SequenceJob::SJ_Filter].toString();
1003 label += filterType;
1011 else if (frameType != FRAME_LIGHT)
1014 label += (char)frameType;
1020QString progressLine(
const SchedulerJob::JobProgress &progress)
1022 QString label = progressLineLabel(progress.type, progress.properties, progress.isDarkFlat).
append(
":");
1024 const double seconds = progress.numCompleted * progress.properties[SequenceJob::SJ_Exposure].
toDouble();
1028 else if (seconds < 60)
1030 else if (seconds < 60 * 60)
1037 return QString(
"%1\t%2 %3 %4")
1038 .
arg(label, -12,
' ')
1039 .
arg(progress.numCompleted, 4)
1045const QString SchedulerJob::getProgressSummary()
const
1048 for (
const auto &p : m_Progress)
1050 summary.
append(progressLine(p));
1058 bool is_setting =
false;
1059 double const alt = SchedulerUtils::findAltitude(getTargetCoords(),
QDateTime(), &is_setting);
1064 {
"pa", m_PositionAngle},
1065 {
"targetRA", getTargetCoords().
ra0().
Hours()},
1066 {
"targetDEC", getTargetCoords().
dec0().
Degrees()},
1069 {
"sequenceCount", sequenceCount},
1070 {
"completedCount", completedCount},
1071 {
"minAltitude", minAltitude},
1072 {
"minMoonSeparation", minMoonSeparation},
1073 {
"repeatsRequired", repeatsRequired},
1074 {
"repeatsRemaining", repeatsRemaining},
1075 {
"inSequenceFocus", inSequenceFocus},
1076 {
"startupTime", startupTime.
isValid() ? startupTime.
toString() :
"--"},
1077 {
"completionTime", finishAtTime.
isValid() ? finishAtTime.
toString() :
"--"},
1079 {
"altitudeFormatted", m_AltitudeFormatted},
1080 {
"startupFormatted", m_StartupFormatted},
1081 {
"endFormatted", m_EndFormatted},
1082 {
"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