6#include "schedulerprocess.h"
7#include "schedulermodulestate.h"
8#include "scheduleradaptor.h"
9#include "greedyscheduler.h"
10#include "schedulerutils.h"
11#include "schedulerjob.h"
12#include "ekos/capture/sequencejob.h"
14#include "ksmessagebox.h"
15#include "ksnotification.h"
17#include "kstarsdata.h"
18#include "indi/indistd.h"
19#include "skymapcomposite.h"
20#include "mosaiccomponent.h"
21#include "mosaictiles.h"
22#include "ekos/auxiliary/opticaltrainmanager.h"
23#include "ekos/auxiliary/stellarsolverprofile.h"
24#include <ekos_scheduler_debug.h>
26#include <QtDBus/QDBusReply>
27#include <QtDBus/QDBusInterface>
29#define RESTART_GUIDING_DELAY_MS 5000
38SchedulerProcess::SchedulerProcess(QSharedPointer<SchedulerModuleState> state,
const QString &ekosPathStr,
39 const QString &ekosInterfaceStr) : QObject(KStars::Instance())
41 setObjectName(
"SchedulerProcess");
42 m_moduleState = state;
43 m_GreedyScheduler =
new GreedyScheduler();
51 connect(moduleState().data(), &SchedulerModuleState::schedulerStateChanged,
this, &SchedulerProcess::newStatus);
52 connect(moduleState().data(), &SchedulerModuleState::newLog,
this, &SchedulerProcess::appendLogText);
55 new SchedulerAdaptor(
this);
58 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"SchedulerProcess failed to register with dbus");
60 setEkosInterface(
new QDBusInterface(kstarsInterfaceString, ekosPathStr, ekosInterfaceStr,
62 setIndiInterface(
new QDBusInterface(kstarsInterfaceString, INDIPathString, INDIInterfaceString,
65 this, SLOT(setINDICommunicationStatus(Ekos::CommunicationStatus)));
67 this, SLOT(setEkosCommunicationStatus(Ekos::CommunicationStatus)));
69 SLOT(registerNewModule(
QString)));
71 SLOT(registerNewDevice(
QString,
int)));
76 return moduleState()->schedulerState();
81 switch (moduleState()->schedulerState())
85 if (!moduleState()->startupScriptURL().isEmpty() && ! moduleState()->startupScriptURL().isValid())
93 if (!moduleState()->shutdownScriptURL().isEmpty() && !moduleState()->shutdownScriptURL().isValid())
101 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Scheduler is starting...";
103 moduleState()->setSchedulerState(SCHEDULER_RUNNING);
104 moduleState()->setupNextIteration(RUN_SCHEDULER);
107 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Scheduler started.";
110 case SCHEDULER_PAUSED:
111 moduleState()->setSchedulerState(SCHEDULER_RUNNING);
112 moduleState()->setupNextIteration(RUN_SCHEDULER);
115 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Scheduler resuming.";
130 if (moduleState()->schedulerState() == SCHEDULER_PAUSED)
141 __FUNCTION__,
"Finding next job requires current to be in error, aborted, idle or complete");
144 moduleState()->resetAlignFailureCount();
145 moduleState()->resetGuideFailureCount();
146 moduleState()->resetFocusFailureCount();
147 moduleState()->resetCaptureFailureCount();
151 emit jobEnded(activeJob()->getName(), activeJob()->getStopReason());
152 moduleState()->resetCaptureBatch();
157 appendLogText(
i18n(
"Job '%1' is terminated due to errors.", activeJob()->getName()));
162 moduleState()->updateJobStage(SCHEDSTAGE_IDLE);
166 bool canResume =
false;
167 if (Options::errorHandlingStrategy() == ERROR_RESTART_IMMEDIATELY &&
169 (activeJob()->getState() ==
SCHEDJOB_ERROR && Options::rescheduleErrors())))
171 const auto oldState = activeJob()->getState();
174 canResume = getGreedyScheduler()->checkJob(moduleState()->jobs(), SchedulerModuleState::getLocalTime().addSecs(30),
176 activeJob()->setState(oldState);
183 appendLogText(
i18n(
"Waiting %1 seconds to restart job '%2'.", Options::errorHandlingStrategyDelay(),
184 activeJob()->getName()));
187 moduleState()->setupNextIteration(RUN_WAKEUP, std::lround((Options::errorHandlingStrategyDelay() * 1000) /
188 KStarsData::Instance()->clock()->scale()));
189 emit changeSleepLabel(
i18n(
"Scheduler waits for a retry."));
194 moduleState()->setActiveJob(
nullptr);
195 moduleState()->setupNextIteration(RUN_SCHEDULER);
199 emit jobEnded(activeJob()->getName(), activeJob()->getStopReason());
202 moduleState()->setActiveJob(
nullptr);
203 moduleState()->setupNextIteration(RUN_SCHEDULER);
207 else if (activeJob()->getCompletionCondition() == FINISH_SEQUENCE)
209 emit jobEnded(activeJob()->getName(), activeJob()->getStopReason());
212 if (Options::rememberJobProgress())
214 foreach(SchedulerJob *a_job, moduleState()->jobs())
215 if (a_job == activeJob() || a_job->isDuplicateOf(activeJob()))
219 moduleState()->resetCaptureBatch();
226 moduleState()->updateJobStage(SCHEDSTAGE_IDLE);
230 if (!canCountCaptures(*activeJob()))
233 moduleState()->setActiveJob(
nullptr);
234 moduleState()->setupNextIteration(RUN_SCHEDULER);
236 else if (activeJob()->getCompletionCondition() == FINISH_REPEAT &&
237 (activeJob()->getRepeatsRemaining() <= 1))
240 if (activeJob()->getRepeatsRemaining() > 0)
243 if (!Options::rememberJobProgress())
245 activeJob()->setRepeatsRemaining(activeJob()->getRepeatsRemaining() - 1);
246 activeJob()->setCompletedIterations(activeJob()->getCompletedIterations() + 1);
248 activeJob()->setStartupTime(
QDateTime());
252 foreach(SchedulerJob *a_job, moduleState()->jobs())
253 if (a_job == activeJob() || a_job->isDuplicateOf(activeJob()))
260 if (activeJob() ==
nullptr || activeJob()->getRepeatsRemaining() == 0)
264 if (activeJob() !=
nullptr)
266 emit jobEnded(activeJob()->getName(), activeJob()->getStopReason());
268 "Job '%1' is complete after #%2 batches.",
269 activeJob()->getName(), activeJob()->getRepeatsRequired()));
270 if (!canCountCaptures(*activeJob()))
272 moduleState()->setActiveJob(
nullptr);
274 moduleState()->setupNextIteration(RUN_SCHEDULER);
284 if (activeJob()->getStepPipeline() & SchedulerJob::USE_ALIGN && Options::forceAlignmentBeforeJob())
287 moduleState()->updateJobStage(SCHEDSTAGE_ALIGNING);
291 else if ( (activeJob()->getStepPipeline() & SchedulerJob::USE_GUIDE) )
293 moduleState()->updateJobStage(SCHEDSTAGE_CAPTURING);
297 else if (activeJob()->getStepPipeline() & SchedulerJob::USE_ALIGN)
299 moduleState()->updateJobStage(SCHEDSTAGE_ALIGNING);
303 else if (activeJob()->getStepPipeline() & SchedulerJob::USE_TRACK)
305 moduleState()->updateJobStage(SCHEDSTAGE_SLEWING);
311 moduleState()->updateJobStage(SCHEDSTAGE_CAPTURING);
316 "Job '%1' is repeating, #%2 batches remaining.",
317 activeJob()->getName(), activeJob()->getRepeatsRemaining()));
319 moduleState()->setupNextIteration(RUN_JOBCHECK);
322 else if ((activeJob()->getCompletionCondition() == FINISH_LOOP) ||
323 (activeJob()->getCompletionCondition() == FINISH_REPEAT &&
324 activeJob()->getRepeatsRemaining() > 0))
327 if ((activeJob()->getCompletionCondition() == FINISH_REPEAT) &&
328 (activeJob()->getRepeatsRemaining() > 1))
331 if (!Options::rememberJobProgress())
333 activeJob()->setRepeatsRemaining(activeJob()->getRepeatsRemaining() - 1);
334 activeJob()->setCompletedIterations(activeJob()->getCompletedIterations() + 1);
336 activeJob()->setStartupTime(
QDateTime());
342 if (activeJob()->getStepPipeline() & SchedulerJob::USE_ALIGN && Options::forceAlignmentBeforeJob())
345 moduleState()->updateJobStage(SCHEDSTAGE_ALIGNING);
350 moduleState()->updateJobStage(SCHEDSTAGE_CAPTURING);
354 moduleState()->increaseCaptureBatch();
356 if (activeJob()->getCompletionCondition() == FINISH_REPEAT )
358 "Job '%1' is repeating, #%2 batches remaining.",
359 activeJob()->getName(), activeJob()->getRepeatsRemaining()));
361 appendLogText(
i18n(
"Job '%1' is repeating, looping indefinitely.", activeJob()->getName()));
364 moduleState()->setupNextIteration(RUN_JOBCHECK);
366 else if (activeJob()->getCompletionCondition() == FINISH_AT)
368 if (SchedulerModuleState::getLocalTime().secsTo(activeJob()->getFinishAtTime()) <= 0)
370 emit jobEnded(activeJob()->getName(), activeJob()->getStopReason());
373 foreach(SchedulerJob *a_job, moduleState()->jobs())
374 if (a_job == activeJob() || a_job->isDuplicateOf(activeJob()))
378 moduleState()->resetCaptureBatch();
380 appendLogText(
i18np(
"Job '%1' stopping, reached completion time with #%2 batch done.",
381 "Job '%1' stopping, reached completion time with #%2 batches done.",
382 activeJob()->getName(), moduleState()->captureBatch() + 1));
385 moduleState()->updateJobStage(SCHEDSTAGE_IDLE);
387 moduleState()->setActiveJob(
nullptr);
388 moduleState()->setupNextIteration(RUN_SCHEDULER);
395 if (activeJob()->getStepPipeline() & SchedulerJob::USE_ALIGN && Options::forceAlignmentBeforeJob())
398 moduleState()->updateJobStage(SCHEDSTAGE_ALIGNING);
403 moduleState()->updateJobStage(SCHEDSTAGE_CAPTURING);
407 moduleState()->increaseCaptureBatch();
409 appendLogText(
i18np(
"Job '%1' completed #%2 batch before completion time, restarted.",
410 "Job '%1' completed #%2 batches before completion time, restarted.",
411 activeJob()->getName(), moduleState()->captureBatch()));
413 moduleState()->setupNextIteration(RUN_JOBCHECK);
419 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"BUGBUG! Job '" << activeJob()->getName() <<
420 "' timer elapsed, but no action to be taken.";
423 moduleState()->updateJobStage(SCHEDSTAGE_IDLE);
425 moduleState()->setActiveJob(
nullptr);
426 moduleState()->setupNextIteration(RUN_SCHEDULER);
430void Ekos::SchedulerProcess::stopCapturing(
QString train,
bool followersOnly)
432 if (train ==
"" && followersOnly)
434 for (
auto key : m_activeJobs.
keys())
437 SchedulerJob *job = m_activeJobs[key];
441 dbusargs.
append(job->getOpticalTrain());
449 QList<QVariant> dbusargs;
454 for (
auto job : m_activeJobs.values())
455 if (train ==
"" || job->getOpticalTrain() == train)
462 if (
nullptr != activeJob())
464 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Job '" << activeJob()->getName() <<
"' is stopping current action..." <<
465 activeJob()->getStage();
467 switch (activeJob()->getStage())
469 case SCHEDSTAGE_IDLE:
472 case SCHEDSTAGE_SLEWING:
476 case SCHEDSTAGE_FOCUSING:
480 case SCHEDSTAGE_ALIGNING:
486 case SCHEDSTAGE_CAPTURING:
495 moduleState()->updateJobStage(SCHEDSTAGE_IDLE);
504 if (moduleState()->preemptiveShutdown())
506 moduleState()->disablePreemptiveShutdown();
512 if (moduleState()->schedulerState() == SCHEDULER_RUNNING)
515 appendLogText(
i18n(
"Scheduler is awake. Jobs shall be started when scheduler is resumed."));
517 moduleState()->setupNextIteration(RUN_SCHEDULER);
524 foreach (
auto j, moduleState()->jobs())
527 emit updateJobTable(j);
529 moduleState()->init();
536 if (moduleState()->schedulerState() != SCHEDULER_RUNNING)
539 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Scheduler is stopping...";
543 if (!moduleState()->preemptiveShutdown())
545 for (
auto &oneJob : moduleState()->jobs())
547 if (oneJob == activeJob())
552 appendLogText(
i18n(
"Job '%1' has not been processed upon scheduler stop, marking aborted.", oneJob->getName()));
558 moduleState()->setupNextIteration(RUN_NOTHING);
559 moduleState()->cancelGuidingTimer();
560 moduleState()->tickleTimer().stop();
562 moduleState()->setSchedulerState(SCHEDULER_IDLE);
563 moduleState()->setParkWaitState(PARKWAIT_IDLE);
564 moduleState()->setEkosState(EKOS_IDLE);
565 moduleState()->setIndiState(INDI_IDLE);
569 if (moduleState()->startupState() != STARTUP_COMPLETE || moduleState()->preemptiveShutdown())
571 if (moduleState()->startupState() == STARTUP_SCRIPT)
573 scriptProcess().disconnect();
574 scriptProcess().terminate();
577 moduleState()->setStartupState(STARTUP_IDLE);
582 else if (moduleState()->startupState() == STARTUP_COMPLETE)
584 if (Options::schedulerUnparkDome())
585 moduleState()->setStartupState(STARTUP_UNPARK_DOME);
586 else if (Options::schedulerUnparkMount())
587 moduleState()->setStartupState(STARTUP_UNPARK_MOUNT);
588 else if (Options::schedulerOpenDustCover())
589 moduleState()->setStartupState(STARTUP_UNPARK_CAP);
592 moduleState()->setShutdownState(SHUTDOWN_IDLE);
594 moduleState()->setActiveJob(
nullptr);
595 moduleState()->resetFailureCounters();
596 moduleState()->resetAutofocusCompleted();
599 if (moduleState()->preemptiveShutdown())
601 QDateTime const now = SchedulerModuleState::getLocalTime();
602 int const nextObservationTime = now.
secsTo(moduleState()->preemptiveShutdownWakeupTime());
603 moduleState()->setupNextIteration(RUN_WAKEUP,
604 std::lround(((nextObservationTime + 1) * 1000)
605 / KStarsData::Instance()->clock()->scale()));
607 emit schedulerStopped();
612 if (captureInterface().isNull() ==
false)
613 captureInterface()->setProperty(
"targetName",
QString());
616 scriptProcess().terminate();
619 emit schedulerStopped();
624 emit clearJobTable();
626 qDeleteAll(moduleState()->jobs());
627 moduleState()->mutlableJobs().clear();
628 moduleState()->setCurrentPosition(-1);
640 emit changeCurrentSequence(sequenceFileURL);
645 if (moduleState()->schedulerState() == SCHEDULER_RUNNING)
649 foreach (SchedulerJob *job, moduleState()->jobs())
650 job->setCompletedCount(0);
658 Q_ASSERT_X(
nullptr != job, __FUNCTION__,
659 "There must be a valid current job for Scheduler to test sleep requirement");
661 if (job->getLightFramesRequired() ==
false)
664 QDateTime const now = SchedulerModuleState::getLocalTime();
665 int const nextObservationTime = now.
secsTo(job->getStartupTime());
669 if (getGreedyScheduler()->getScheduledJob() != job)
673 if (Options::schedulerWeather())
675 ISD::Weather::Status weatherStatus = moduleState()->weatherStatus();
676 if (weatherStatus == ISD::Weather::WEATHER_WARNING || weatherStatus == ISD::Weather::WEATHER_ALERT)
679 if (moduleState()->weatherGracePeriodActive())
681 appendLogText(
i18n(
"Job '%1' cannot start because weather status is %2 and grace period is over.",
682 job->getName(), (weatherStatus == ISD::Weather::WEATHER_WARNING) ?
i18n(
"Warning") :
i18n(
"Alert")));
684 moduleState()->setWeatherGracePeriodActive(
false);
689 QDateTime wakeupTime = SchedulerModuleState::getLocalTime().
addSecs(Options::schedulerWeatherGracePeriod() * 60);
691 appendLogText(
i18n(
"Job '%1' cannot start because weather status is %2. Waiting until weather improves or until %3",
692 job->getName(), (weatherStatus == ISD::Weather::WEATHER_WARNING) ?
i18n(
"Warning") :
i18n(
"Alert"),
696 moduleState()->setWeatherGracePeriodActive(
true);
697 moduleState()->enablePreemptiveShutdown(wakeupTime);
699 emit schedulerSleeping(
true,
true);
704 moduleState()->setWeatherGracePeriodActive(
false);
708 if (moduleState()->startupState() == STARTUP_COMPLETE &&
709 Options::preemptiveShutdown() &&
710 nextObservationTime > (Options::preemptiveShutdownTime() * 3600))
713 "Job '%1' scheduled for execution at %2. "
714 "Observatory scheduled for shutdown until next job is ready.",
715 job->getName(), job->getStartupTime().
toString()));
716 moduleState()->enablePreemptiveShutdown(job->getStartupTime());
718 emit schedulerSleeping(
true,
false);
727 else if (nextObservationTime > Options::leadTime() * 60 &&
728 moduleState()->startupState() == STARTUP_COMPLETE &&
729 moduleState()->parkWaitState() == PARKWAIT_IDLE &&
730 (job->getStepPipeline() & SchedulerJob::USE_TRACK) &&
732 Options::schedulerParkMount())
735 "Job '%1' scheduled for execution at %2. "
736 "Parking the mount until the job is ready.",
737 job->getName(), job->getStartupTime().
toString()));
739 moduleState()->setParkWaitState(PARKWAIT_PARK);
743 else if (nextObservationTime > Options::leadTime() * 60)
745 auto log =
i18n(
"Sleeping until observation job %1 is ready at %2", job->getName(),
748 KSNotification::event(
QLatin1String(
"SchedulerSleeping"), log, KSNotification::Scheduler,
749 KSNotification::Info);
752 if (nextObservationTime > Options::leadTime() * 60 * 12 && !Options::preemptiveShutdown())
754 dms delay(
static_cast<double>(nextObservationTime * 15.0 / 3600.0));
756 "Warning: Job '%1' is %2 away from now, you may want to enable Preemptive Shutdown.",
765 moduleState()->setupNextIteration(RUN_WAKEUP,
766 std::lround(((nextObservationTime + 1) * 1000) / KStarsData::Instance()->clock()->scale()));
768 emit schedulerSleeping(
false,
true);
777 Q_ASSERT_X(
nullptr != activeJob(), __FUNCTION__,
"Job starting slewing must be valid");
782 moduleState()->setParkWaitState(PARKWAIT_UNPARK);
786 if (Options::resetMountModelBeforeJob())
791 SkyPoint target = activeJob()->getTargetCoords();
801 qCCritical(KSTARS_EKOS_SCHEDULER) <<
QString(
"Warning: job '%1' slew request received DBUS error: %2").
arg(
808 moduleState()->updateJobStage(SCHEDSTAGE_SLEWING);
815 Q_ASSERT_X(
nullptr != activeJob(), __FUNCTION__,
"Job starting focusing must be valid");
818 if (activeJob()->getStage() == SCHEDSTAGE_RESLEWING_COMPLETE ||
819 activeJob()->getStage() == SCHEDSTAGE_POSTALIGN_FOCUSING)
825 moduleState()->updateJobStage(SCHEDSTAGE_POSTALIGN_FOCUSING_COMPLETE);
830 if (activeJob()->getOpticalTrain() !=
"")
831 m_activeJobs.insert(activeJob()->getOpticalTrain(), activeJob());
834 QVariant opticalTrain = captureInterface()->property(
"opticalTrain");
836 if (opticalTrain.
isValid() ==
false)
838 qCCritical(KSTARS_EKOS_SCHEDULER) <<
QString(
"Warning: job '%1' opticalTrain request failed.").
arg(activeJob()->getName());
847 m_activeJobs.insert(opticalTrain.
toString(), activeJob());
848 activeJob()->setOpticalTrain(opticalTrain.
toString());
854 foreach (
auto follower, activeJob()->followerJobs())
856 m_activeJobs.insert(follower->getOpticalTrain(), follower);
868 dBusArgs.
append(job->getOpticalTrain());
869 boolReply = focusInterface()->callWithArgumentList(
QDBus::AutoDetect,
"canAutoFocus", dBusArgs);
873 qCCritical(KSTARS_EKOS_SCHEDULER) <<
QString(
"Warning: job '%1' canAutoFocus request received DBUS error: %2").
arg(
883 if (boolReply.value() ==
false)
885 appendLogText(
i18n(
"Warning: job '%1' is unable to proceed with autofocus, not supported.", job->getName()));
886 job->setStepPipeline(
887 static_cast<SchedulerJob::StepPipeline
>(job->getStepPipeline() & ~SchedulerJob::USE_FOCUS));
888 moduleState()->setAutofocusCompleted(job->getOpticalTrain(),
true);
889 if (moduleState()->autofocusCompleted())
891 moduleState()->updateJobStage(SCHEDSTAGE_FOCUS_COMPLETE);
900 if ((reply = captureInterface()->callWithArgumentList(
QDBus::AutoDetect,
"clearAutoFocusHFR",
903 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: job '%1' clearAutoFocusHFR request received DBUS error: %2").arg(
914 if ((reply = focusInterface()->callWithArgumentList(
QDBus::AutoDetect,
"resetFrame",
917 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: job '%1' resetFrame request received DBUS error: %2").arg(
929 if (!job->getInitialFilter().
isEmpty())
932 dBusArgs.
append(job->getInitialFilter());
933 dBusArgs.
append(job->getOpticalTrain());
934 if ((reply = focusInterface()->callWithArgumentList(
QDBus::AutoDetect,
"setFilter",
937 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: job '%1' setFilter request received DBUS error: %1").arg(
949 dBusArgs.
append(job->getOpticalTrain());
950 boolReply = focusInterface()->callWithArgumentList(
QDBus::AutoDetect,
"useFullField", dBusArgs);
954 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: job '%1' useFullField request received DBUS error: %2").arg(
964 if (boolReply.value() ==
false)
969 dBusArgs.
append(job->getOpticalTrain());
970 if ((reply = focusInterface()->callWithArgumentList(
QDBus::AutoDetect,
"setAutoStarEnabled", dBusArgs)).
type() ==
973 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: job '%1' setAutoFocusStar request received DBUS error: %1").arg(
986 dBusArgs.
append(job->getOpticalTrain());
990 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: job '%1' startFocus request received DBUS error: %2").arg(
1000 moduleState()->updateJobStage(SCHEDSTAGE_FOCUSING);
1002 moduleState()->startCurrentOperationTimer();
1007 Q_ASSERT_X(
nullptr != activeJob(), __FUNCTION__,
"Job starting aligning must be valid");
1018 moduleState()->setIndexToUse(-1);
1019 moduleState()->setHealpixToUse(-1);
1022 if (activeJob()->getFITSFile().isEmpty() ==
false)
1028 appendLogText(
i18n(
"Warning: job '%1' target FITS file does not exist.", activeJob()->getName()));
1037 if ((reply = alignInterface()->callWithArgumentList(
QDBus::AutoDetect,
"loadAndSlew", solveArgs)).type() ==
1040 appendLogText(
i18n(
"Warning: job '%1' loadAndSlew request received DBUS error: %2",
1049 else if (reply.
arguments().first().toBool() ==
false)
1051 appendLogText(
i18n(
"Warning: job '%1' loadAndSlew request failed.", activeJob()->getName()));
1057 appendLogText(
i18n(
"Job '%1' is plate solving %2.", activeJob()->getName(), activeJob()->getFITSFile().fileName()));
1063 const SkyPoint targetCoords = activeJob()->getTargetCoords();
1066 rotationArgs << activeJob()->getPositionAngle();
1068 if ((reply = alignInterface()->callWithArgumentList(
QDBus::AutoDetect,
"setTargetCoords",
1071 appendLogText(
i18n(
"Warning: job '%1' setTargetCoords request received DBUS error: %2",
1082 if (activeJob()->getPositionAngle() >= -180)
1084 if ((reply = alignInterface()->callWithArgumentList(
QDBus::AutoDetect,
"setTargetPositionAngle",
1087 appendLogText(
i18n(
"Warning: job '%1' setTargetPositionAngle request received DBUS error: %2").arg(
1100 appendLogText(
i18n(
"Warning: job '%1' captureAndSolve request received DBUS error: %2").arg(
1109 else if (reply.
arguments().first().toBool() ==
false)
1111 appendLogText(
i18n(
"Warning: job '%1' captureAndSolve request failed.", activeJob()->getName()));
1117 appendLogText(
i18n(
"Job '%1' is capturing and plate solving.", activeJob()->getName()));
1121 moduleState()->updateJobStage(SCHEDSTAGE_ALIGNING);
1122 moduleState()->startCurrentOperationTimer();
1127 Q_ASSERT_X(
nullptr != activeJob(), __FUNCTION__,
"Job starting guiding must be valid");
1132 moduleState()->updateJobStage(SCHEDSTAGE_GUIDING_COMPLETE);
1133 appendLogText(
i18n(
"Guiding already running for %1, starting next scheduler action...", activeJob()->getName()));
1135 moduleState()->startCurrentOperationTimer();
1148 if (resetCalibration && Options::resetGuideCalibration())
1155 moduleState()->updateJobStage(SCHEDSTAGE_GUIDING);
1157 appendLogText(
i18n(
"Starting guiding procedure for %1 ...", activeJob()->getName()));
1159 moduleState()->startCurrentOperationTimer();
1164 if (!guideInterface())
1168 if (
nullptr != activeJob() && (activeJob()->getStepPipeline() & SchedulerJob::USE_GUIDE))
1170 qCInfo(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' is stopping guiding...").
arg(activeJob()->getName());
1172 moduleState()->resetGuideFailureCount();
1174 stopCapturing(
"",
true);
1178 if (moduleState()->isGuidingTimerActive())
1179 moduleState()->cancelGuidingTimer();
1184 if ((moduleState()->restartGuidingInterval() > 0) &&
1185 (moduleState()->restartGuidingTime().msecsTo(KStarsData::Instance()->ut()) > moduleState()->restartGuidingInterval()))
1187 moduleState()->cancelGuidingTimer();
1194 Q_ASSERT_X(
nullptr != activeJob(), __FUNCTION__,
"Job starting capturing must be valid");
1197 if (activeJob()->getStepPipeline() & SchedulerJob::USE_GUIDE &&
getGuidingStatus() != GUIDE_GUIDING)
1200 moduleState()->updateJobStage(SCHEDSTAGE_GUIDING);
1205 startSingleCapture(activeJob(), restart);
1206 for (
auto follower : activeJob()->followerJobs())
1209 if (follower->getState() ==
SCHEDJOB_SCHEDULED || (follower->getStage() == SCHEDSTAGE_CAPTURING && follower->isStopped()))
1212 follower->setStage(SCHEDSTAGE_CAPTURING);
1213 startSingleCapture(follower, restart);
1217 moduleState()->updateJobStage(SCHEDSTAGE_CAPTURING);
1219 KSNotification::event(
QLatin1String(
"EkosScheduledImagingStart"),
1220 i18n(
"Ekos job (%1) - Capture started", activeJob()->getName()), KSNotification::Scheduler);
1222 if (moduleState()->captureBatch() > 0)
1223 appendLogText(
i18n(
"Job '%1' capture is in progress (batch #%2)...", activeJob()->getName(),
1224 moduleState()->captureBatch() + 1));
1226 appendLogText(
i18n(
"Job '%1' capture is in progress...", activeJob()->getName()));
1228 moduleState()->startCurrentOperationTimer();
1231void SchedulerProcess::startSingleCapture(SchedulerJob *job,
bool restart)
1233 captureInterface()->setProperty(
"targetName", job->getName());
1236 QVariant train(job->getOpticalTrain());
1238 if (restart ==
false)
1243 QVariant targetName(job->getName());
1247 dbusargs.
append(targetName);
1249 "loadSequenceQueue",
1253 qCCritical(KSTARS_EKOS_SCHEDULER) <<
1254 QString(
"Warning: job '%1' loadSequenceQueue request received DBUS error: %1").
arg(job->getName()).
arg(
1261 else if (captureReply.value() ==
false)
1263 qCCritical(KSTARS_EKOS_SCHEDULER) <<
1264 QString(
"Warning: job '%1' loadSequenceQueue request failed").
arg(job->getName());
1273 for (
auto &e : fMap.keys())
1275 QList<QVariant> dbusargs;
1278 dbusargs.
append(fMap.value(e));
1281 if ((reply = captureInterface()->callWithArgumentList(
QDBus::Block,
"setCapturedFramesMap",
1282 dbusargs)).
type() ==
1285 qCCritical(KSTARS_EKOS_SCHEDULER) <<
1286 QString(
"Warning: job '%1' setCapturedFramesCount request received DBUS error: %1").arg(job->getName()).arg(
1295 QList<QVariant> dbusargs;
1298 QDBusReply<QString>
const startReply = captureInterface()->callWithArgumentList(
QDBus::AutoDetect,
"start",
1303 qCCritical(KSTARS_EKOS_SCHEDULER) <<
1304 QString(
"Warning: job '%1' start request received DBUS error: %1").arg(job->getName()).arg(
1311 QString trainName = startReply.value();
1312 m_activeJobs[trainName] = job;
1318 QVariant gotoMode(
static_cast<int>(mode));
1324 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Loading profiles";
1328 moduleState()->updateProfiles(profiles);
1331void SchedulerProcess::executeScript(
const QString &filename)
1340 checkProcessExit(exitCode);
1344 scriptProcess().
start(filename, arguments);
1349 if (moduleState()->schedulerState() == SCHEDULER_PAUSED)
1352 switch (moduleState()->ekosState())
1356 if (moduleState()->ekosCommunicationStatus() == Ekos::Success)
1358 moduleState()->setEkosState(EKOS_READY);
1364 moduleState()->setEkosState(EKOS_STARTING);
1365 moduleState()->startCurrentOperationTimer();
1367 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Ekos communication status is" << moduleState()->ekosCommunicationStatus() <<
1376 if (moduleState()->ekosCommunicationStatus() == Ekos::Success)
1379 moduleState()->resetEkosConnectFailureCount();
1380 moduleState()->setEkosState(EKOS_READY);
1383 else if (moduleState()->ekosCommunicationStatus() == Ekos::Error)
1385 if (moduleState()->increaseEkosConnectFailureCount())
1396 else if (moduleState()->ekosCommunicationStatus() == Ekos::Idle)
1399 else if (moduleState()->getCurrentOperationMsec() > (60 * 1000))
1401 if (moduleState()->increaseEkosConnectFailureCount())
1408 moduleState()->startCurrentOperationTimer();
1422 if (moduleState()->ekosCommunicationStatus() == Ekos::Idle)
1425 moduleState()->setEkosState(EKOS_IDLE);
1439 if (moduleState()->schedulerState() == SCHEDULER_PAUSED)
1442 switch (moduleState()->indiState())
1446 if (moduleState()->indiCommunicationStatus() == Ekos::Success)
1448 moduleState()->setIndiState(INDI_PROPERTY_CHECK);
1449 moduleState()->resetIndiConnectFailureCount();
1450 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Checking INDI Properties...";
1454 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Connecting INDI devices...";
1456 moduleState()->setIndiState(INDI_CONNECTING);
1458 moduleState()->startCurrentOperationTimer();
1463 case INDI_CONNECTING:
1465 if (moduleState()->indiCommunicationStatus() == Ekos::Success)
1468 moduleState()->setIndiState(INDI_PROPERTY_CHECK);
1470 else if (moduleState()->indiCommunicationStatus() == Ekos::Error)
1472 if (moduleState()->increaseIndiConnectFailureCount() <= moduleState()->maxFailureAttempts())
1479 appendLogText(
i18n(
"One or more INDI devices failed to connect. Check INDI control panel for details."));
1484 else if (moduleState()->getCurrentOperationMsec() > (30 * 1000))
1486 if (moduleState()->increaseIndiConnectFailureCount() <= moduleState()->maxFailureAttempts())
1490 moduleState()->startCurrentOperationTimer();
1494 appendLogText(
i18n(
"One or more INDI devices timed out. Check INDI control panel for details."));
1501 case INDI_DISCONNECTING:
1503 if (moduleState()->indiCommunicationStatus() == Ekos::Idle)
1506 moduleState()->setIndiState(INDI_IDLE);
1512 case INDI_PROPERTY_CHECK:
1514 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Checking INDI properties.";
1516 if (Options::schedulerUnparkDome() && moduleState()->domeReady() ==
false)
1518 if (moduleState()->getCurrentOperationMsec() > (30 * 1000))
1520 moduleState()->startCurrentOperationTimer();
1521 appendLogText(
i18n(
"Warning: dome device not ready after timeout, attempting to recover..."));
1531 if (Options::schedulerUnparkMount() && moduleState()->mountReady() ==
false)
1533 if (moduleState()->getCurrentOperationMsec() > (30 * 1000))
1535 moduleState()->startCurrentOperationTimer();
1536 appendLogText(
i18n(
"Warning: mount device not ready after timeout, attempting to recover..."));
1541 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Mount unpark required but mount is not yet ready.";
1546 if (Options::schedulerOpenDustCover() && moduleState()->capReady() ==
false)
1548 if (moduleState()->getCurrentOperationMsec() > (30 * 1000))
1550 moduleState()->startCurrentOperationTimer();
1551 appendLogText(
i18n(
"Warning: cap device not ready after timeout, attempting to recover..."));
1556 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Cap unpark required but cap is not yet ready.";
1561 if (captureInterface().isNull())
1564 if (moduleState()->captureReady() ==
false)
1566 QVariant hasCoolerControl = captureInterface()->property(
"coolerControl");
1567 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Cooler control" << (!hasCoolerControl.
isValid() ?
"invalid" :
1568 (hasCoolerControl.
toBool() ?
"True" :
"Faklse"));
1569 if (hasCoolerControl.
isValid())
1570 moduleState()->setCaptureReady(
true);
1572 qCWarning(KSTARS_EKOS_SCHEDULER) <<
"Capture module is not ready yet...";
1575 moduleState()->setIndiState(INDI_READY);
1576 moduleState()->resetIndiConnectFailureCount();
1590 if (moduleState()->indiState() == INDI_DISCONNECTING
1595 if (moduleState()->weatherGracePeriodActive() ==
false)
1598 if (moduleState()->indiState() != INDI_IDLE && Options::stopEkosAfterShutdown())
1605 if (moduleState()->ekosState() == EKOS_STOPPING &&
checkEkosState() ==
false)
1609 if (moduleState()->ekosState() != EKOS_IDLE && Options::stopEkosAfterShutdown())
1616 if (moduleState()->shutdownState() == SHUTDOWN_COMPLETE)
1629 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Disconnecting INDI...";
1630 moduleState()->setIndiState(INDI_DISCONNECTING);
1634void SchedulerProcess::stopEkos()
1636 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Stopping Ekos...";
1637 moduleState()->setEkosState(EKOS_STOPPING);
1638 moduleState()->resetEkosConnectFailureCount();
1640 moduleState()->setMountReady(
false);
1641 moduleState()->setCaptureReady(
false);
1642 moduleState()->setDomeReady(
false);
1643 moduleState()->setCapReady(
false);
1648 if (SCHEDULER_RUNNING != moduleState()->schedulerState())
1652 switch (moduleState()->ekosState())
1663 switch (moduleState()->indiState())
1666 case INDI_DISCONNECTING:
1675 if (moduleState()->ekosCommunicationStatus() == Ekos::Success)
1677 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Ekos is currently connected, checking INDI before mitigating connection loss.");
1680 if (moduleState()->isINDIConnected())
1683 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"INDI is currently connected, no connection loss mitigation needed.");
1702 if (capInterface().isNull())
1705 QVariant parkingStatus = capInterface()->property(
"parkStatus");
1706 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
1708 if (parkingStatus.
isValid() ==
false)
1710 qCCritical(KSTARS_EKOS_SCHEDULER) <<
QString(
"Warning: cap parkStatus request received DBUS error: %1").
arg(
1711 capInterface()->lastError().type());
1713 parkingStatus = ISD::PARK_ERROR;
1716 ISD::ParkStatus status =
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt());
1720 case ISD::PARK_PARKED:
1721 if (moduleState()->shutdownState() == SHUTDOWN_PARKING_CAP)
1724 moduleState()->setShutdownState(SHUTDOWN_PARK_MOUNT);
1726 moduleState()->resetParkingCapFailureCount();
1729 case ISD::PARK_UNPARKED:
1730 if (moduleState()->startupState() == STARTUP_UNPARKING_CAP)
1732 moduleState()->setStartupState(STARTUP_COMPLETE);
1735 moduleState()->resetParkingCapFailureCount();
1738 case ISD::PARK_PARKING:
1739 case ISD::PARK_UNPARKING:
1741 if (moduleState()->getCurrentOperationMsec() > (60 * 1000))
1743 if (moduleState()->increaseParkingCapFailureCount())
1746 if (status == ISD::PARK_PARKING)
1755 case ISD::PARK_ERROR:
1756 if (moduleState()->shutdownState() == SHUTDOWN_PARKING_CAP)
1759 moduleState()->setShutdownState(SHUTDOWN_ERROR);
1761 else if (moduleState()->startupState() == STARTUP_UNPARKING_CAP)
1764 moduleState()->setStartupState(STARTUP_ERROR);
1766 moduleState()->resetParkingCapFailureCount();
1774void SchedulerProcess::checkMountParkingStatus()
1776 if (mountInterface().isNull())
1779 QVariant parkingStatus = mountInterface()->property(
"parkStatus");
1780 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Mount parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
1782 if (parkingStatus.
isValid() ==
false)
1784 qCCritical(KSTARS_EKOS_SCHEDULER) <<
QString(
"Warning: mount parkStatus request received DBUS error: %1").
arg(
1785 mountInterface()->lastError().type());
1787 moduleState()->setParkWaitState(PARKWAIT_ERROR);
1790 ISD::ParkStatus status =
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt());
1795 case ISD::PARK_PARKED:
1798 if (moduleState()->shutdownState() == SHUTDOWN_PARKING_MOUNT)
1799 moduleState()->setShutdownState(SHUTDOWN_PARK_DOME);
1802 if (moduleState()->parkWaitState() == PARKWAIT_PARKING)
1803 moduleState()->setParkWaitState(PARKWAIT_PARKED);
1806 moduleState()->resetParkingMountFailureCount();
1810 case ISD::PARK_UNPARKED:
1813 if (moduleState()->startupState() == STARTUP_UNPARKING_MOUNT)
1814 moduleState()->setStartupState(STARTUP_UNPARK_CAP);
1817 if (moduleState()->parkWaitState() == PARKWAIT_UNPARKING)
1818 moduleState()->setParkWaitState(PARKWAIT_UNPARKED);
1821 moduleState()->resetParkingMountFailureCount();
1827 case ISD::PARK_UNPARKING:
1828 if (moduleState()->getCurrentOperationMsec() > (60 * 1000))
1830 if (moduleState()->increaseParkingMountFailureCount())
1832 appendLogText(
i18n(
"Warning: mount unpark operation timed out on attempt %1/%2. Restarting operation...",
1833 moduleState()->parkingMountFailureCount(), moduleState()->maxFailureAttempts()));
1838 appendLogText(
i18n(
"Warning: mount unpark operation timed out on last attempt."));
1839 moduleState()->setParkWaitState(PARKWAIT_ERROR);
1842 else qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Unparking mount in progress...";
1847 case ISD::PARK_PARKING:
1848 if (moduleState()->getCurrentOperationMsec() > (60 * 1000))
1850 if (moduleState()->increaseParkingMountFailureCount())
1852 appendLogText(
i18n(
"Warning: mount park operation timed out on attempt %1/%2. Restarting operation...",
1853 moduleState()->parkingMountFailureCount(),
1854 moduleState()->maxFailureAttempts()));
1859 appendLogText(
i18n(
"Warning: mount park operation timed out on last attempt."));
1860 moduleState()->setParkWaitState(PARKWAIT_ERROR);
1863 else qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Parking mount in progress...";
1868 case ISD::PARK_ERROR:
1869 if (moduleState()->startupState() == STARTUP_UNPARKING_MOUNT)
1872 moduleState()->setStartupState(STARTUP_ERROR);
1873 moduleState()->resetParkingMountFailureCount();
1875 else if (moduleState()->shutdownState() == SHUTDOWN_PARKING_MOUNT)
1877 if (moduleState()->increaseParkingMountFailureCount())
1879 appendLogText(
i18n(
"Warning: mount park operation failed on attempt %1/%2. Restarting operation...",
1880 moduleState()->parkingMountFailureCount(),
1881 moduleState()->maxFailureAttempts()));
1887 moduleState()->setShutdownState(SHUTDOWN_ERROR);
1888 moduleState()->resetParkingMountFailureCount();
1892 else if (moduleState()->parkWaitState() == PARKWAIT_PARKING)
1895 moduleState()->setParkWaitState(PARKWAIT_ERROR);
1896 moduleState()->resetParkingMountFailureCount();
1898 else if (moduleState()->parkWaitState() == PARKWAIT_UNPARKING)
1901 moduleState()->setParkWaitState(PARKWAIT_ERROR);
1902 moduleState()->resetParkingMountFailureCount();
1908 case ISD::PARK_UNKNOWN:
1910 if (moduleState()->shutdownState() == SHUTDOWN_PARKING_MOUNT)
1911 moduleState()->setShutdownState(SHUTDOWN_PARK_DOME);
1914 if (moduleState()->startupState() == STARTUP_UNPARKING_MOUNT)
1915 moduleState()->setStartupState(STARTUP_UNPARK_CAP);
1918 if (moduleState()->parkWaitState() == PARKWAIT_PARKING)
1919 moduleState()->setParkWaitState(PARKWAIT_PARKED);
1920 else if (moduleState()->parkWaitState() == PARKWAIT_UNPARKING)
1921 moduleState()->setParkWaitState(PARKWAIT_UNPARKED);
1923 moduleState()->resetParkingMountFailureCount();
1928void SchedulerProcess::checkDomeParkingStatus()
1930 if (domeInterface().isNull())
1933 QVariant parkingStatus = domeInterface()->property(
"parkStatus");
1934 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Dome parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
1936 if (parkingStatus.
isValid() ==
false)
1938 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: dome parkStatus request received DBUS error: %1").arg(
1939 mountInterface()->lastError().
type());
1941 moduleState()->setParkWaitState(PARKWAIT_ERROR);
1944 ISD::ParkStatus status =
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt());
1948 case ISD::PARK_PARKED:
1949 if (moduleState()->shutdownState() == SHUTDOWN_PARKING_DOME)
1953 moduleState()->setShutdownState(SHUTDOWN_SCRIPT);
1955 moduleState()->resetParkingDomeFailureCount();
1958 case ISD::PARK_UNPARKED:
1959 if (moduleState()->startupState() == STARTUP_UNPARKING_DOME)
1961 moduleState()->setStartupState(STARTUP_UNPARK_MOUNT);
1964 moduleState()->resetParkingDomeFailureCount();
1967 case ISD::PARK_PARKING:
1968 case ISD::PARK_UNPARKING:
1970 if (moduleState()->getCurrentOperationMsec() > (120 * 1000))
1972 if (moduleState()->increaseParkingDomeFailureCount())
1975 if (status == ISD::PARK_PARKING)
1984 case ISD::PARK_ERROR:
1985 if (moduleState()->shutdownState() == SHUTDOWN_PARKING_DOME)
1987 if (moduleState()->increaseParkingDomeFailureCount())
1995 moduleState()->setShutdownState(SHUTDOWN_ERROR);
1996 moduleState()->resetParkingDomeFailureCount();
1999 else if (moduleState()->startupState() == STARTUP_UNPARKING_DOME)
2001 if (moduleState()->increaseParkingDomeFailureCount())
2009 moduleState()->setStartupState(STARTUP_ERROR);
2010 moduleState()->resetParkingDomeFailureCount();
2022 if (moduleState()->schedulerState() == SCHEDULER_PAUSED)
2025 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Checking Startup State (%1)...").
arg(moduleState()->startupState());
2027 switch (moduleState()->startupState())
2031 KSNotification::event(
QLatin1String(
"ObservatoryStartup"),
i18n(
"Observatory is in the startup process"),
2032 KSNotification::Scheduler);
2034 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Startup Idle. Starting startup process...";
2041 if (Options::alwaysExecuteStartupScript() ==
false && moduleState()->ekosCommunicationStatus() == Ekos::Success)
2043 if (moduleState()->startupScriptURL().isEmpty() ==
false)
2046 if (!activeJob() || activeJob()->getLightFramesRequired())
2047 moduleState()->setStartupState(STARTUP_UNPARK_DOME);
2049 moduleState()->setStartupState(STARTUP_COMPLETE);
2053 if (moduleState()->currentProfile() !=
i18n(
"Default"))
2056 profile.
append(moduleState()->currentProfile());
2057 ekosInterface()->callWithArgumentList(
QDBus::AutoDetect,
"setProfile", profile);
2060 if (moduleState()->startupScriptURL().isEmpty() ==
false)
2062 moduleState()->setStartupState(STARTUP_SCRIPT);
2067 moduleState()->setStartupState(STARTUP_UNPARK_DOME);
2071 case STARTUP_SCRIPT:
2074 case STARTUP_UNPARK_DOME:
2078 if (activeJob() ==
nullptr || activeJob()->getLightFramesRequired())
2080 if (Options::schedulerUnparkDome())
2083 moduleState()->setStartupState(STARTUP_UNPARK_MOUNT);
2087 moduleState()->setStartupState(STARTUP_COMPLETE);
2093 case STARTUP_UNPARKING_DOME:
2094 checkDomeParkingStatus();
2097 case STARTUP_UNPARK_MOUNT:
2098 if (Options::schedulerUnparkMount())
2101 moduleState()->setStartupState(STARTUP_UNPARK_CAP);
2104 case STARTUP_UNPARKING_MOUNT:
2105 checkMountParkingStatus();
2108 case STARTUP_UNPARK_CAP:
2109 if (Options::schedulerOpenDustCover())
2112 moduleState()->setStartupState(STARTUP_COMPLETE);
2115 case STARTUP_UNPARKING_CAP:
2119 case STARTUP_COMPLETE:
2132 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Checking shutdown state...";
2134 if (moduleState()->schedulerState() == SCHEDULER_PAUSED)
2137 switch (moduleState()->shutdownState())
2141 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Starting shutdown process...";
2143 moduleState()->setActiveJob(
nullptr);
2144 moduleState()->setupNextIteration(RUN_SHUTDOWN);
2145 emit shutdownStarted();
2147 if (Options::schedulerWarmCCD())
2154 if (captureInterface())
2156 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Setting coolerControl=false";
2157 captureInterface()->setProperty(
"coolerControl",
false);
2162 if (moduleState()->isINDIConnected())
2164 if (Options::schedulerCloseDustCover())
2166 moduleState()->setShutdownState(SHUTDOWN_PARK_CAP);
2170 if (Options::schedulerParkMount())
2172 moduleState()->setShutdownState(SHUTDOWN_PARK_MOUNT);
2176 if (Options::schedulerParkDome())
2178 moduleState()->setShutdownState(SHUTDOWN_PARK_DOME);
2182 else appendLogText(
i18n(
"Warning: Bypassing parking procedures, no INDI connection."));
2184 if (moduleState()->shutdownScriptURL().isEmpty() ==
false)
2186 moduleState()->setShutdownState(SHUTDOWN_SCRIPT);
2190 moduleState()->setShutdownState(SHUTDOWN_COMPLETE);
2193 case SHUTDOWN_PARK_CAP:
2194 if (!moduleState()->isINDIConnected())
2196 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Bypassing shutdown step 'park cap', no INDI connection.";
2197 moduleState()->setShutdownState(SHUTDOWN_SCRIPT);
2199 else if (Options::schedulerCloseDustCover())
2202 moduleState()->setShutdownState(SHUTDOWN_PARK_MOUNT);
2205 case SHUTDOWN_PARKING_CAP:
2209 case SHUTDOWN_PARK_MOUNT:
2210 if (!moduleState()->isINDIConnected())
2212 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Bypassing shutdown step 'park cap', no INDI connection.";
2213 moduleState()->setShutdownState(SHUTDOWN_SCRIPT);
2215 else if (Options::schedulerParkMount())
2218 moduleState()->setShutdownState(SHUTDOWN_PARK_DOME);
2221 case SHUTDOWN_PARKING_MOUNT:
2222 checkMountParkingStatus();
2225 case SHUTDOWN_PARK_DOME:
2226 if (!moduleState()->isINDIConnected())
2228 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Bypassing shutdown step 'park cap', no INDI connection.";
2229 moduleState()->setShutdownState(SHUTDOWN_SCRIPT);
2231 else if (Options::schedulerParkDome())
2234 moduleState()->setShutdownState(SHUTDOWN_SCRIPT);
2237 case SHUTDOWN_PARKING_DOME:
2238 checkDomeParkingStatus();
2241 case SHUTDOWN_SCRIPT:
2242 if (moduleState()->shutdownScriptURL().isEmpty() ==
false)
2245 if (moduleState()->ekosState() != EKOS_IDLE && Options::shutdownScriptTerminatesINDI())
2251 moduleState()->setShutdownState(SHUTDOWN_SCRIPT_RUNNING);
2255 moduleState()->setShutdownState(SHUTDOWN_COMPLETE);
2258 case SHUTDOWN_SCRIPT_RUNNING:
2261 case SHUTDOWN_COMPLETE:
2264 case SHUTDOWN_ERROR:
2274 if (moduleState()->schedulerState() == SCHEDULER_PAUSED)
2277 if (moduleState()->parkWaitState() == PARKWAIT_IDLE)
2282 switch (moduleState()->parkWaitState())
2288 case PARKWAIT_PARKING:
2289 checkMountParkingStatus();
2292 case PARKWAIT_UNPARK:
2296 case PARKWAIT_UNPARKING:
2297 checkMountParkingStatus();
2301 case PARKWAIT_PARKED:
2302 case PARKWAIT_UNPARKED:
2305 case PARKWAIT_ERROR:
2317 if (moduleState()->startupState() == STARTUP_IDLE
2318 || moduleState()->startupState() == STARTUP_ERROR
2319 || moduleState()->startupState() == STARTUP_COMPLETE)
2323 KSMessageBox::Instance()->disconnect(
this);
2326 moduleState()->setStartupState(STARTUP_IDLE);
2332 KSMessageBox::Instance()->questionYesNo(
i18n(
"Are you sure you want to execute the startup procedure manually?"));
2336 switch (moduleState()->startupState())
2341 case STARTUP_SCRIPT:
2342 scriptProcess().terminate();
2345 case STARTUP_UNPARK_DOME:
2348 case STARTUP_UNPARKING_DOME:
2349 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Aborting unparking dome...";
2353 case STARTUP_UNPARK_MOUNT:
2356 case STARTUP_UNPARKING_MOUNT:
2357 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Aborting unparking mount...";
2361 case STARTUP_UNPARK_CAP:
2364 case STARTUP_UNPARKING_CAP:
2367 case STARTUP_COMPLETE:
2374 moduleState()->setStartupState(STARTUP_IDLE);
2383 if (moduleState()->shutdownState() == SHUTDOWN_IDLE
2384 || moduleState()->shutdownState() == SHUTDOWN_ERROR
2385 || moduleState()->shutdownState() == SHUTDOWN_COMPLETE)
2389 KSMessageBox::Instance()->disconnect(
this);
2391 moduleState()->setShutdownState(SHUTDOWN_IDLE);
2396 KSMessageBox::Instance()->questionYesNo(
i18n(
"Are you sure you want to execute the shutdown procedure manually?"));
2400 switch (moduleState()->shutdownState())
2405 case SHUTDOWN_SCRIPT:
2408 case SHUTDOWN_SCRIPT_RUNNING:
2409 scriptProcess().terminate();
2412 case SHUTDOWN_PARK_DOME:
2415 case SHUTDOWN_PARKING_DOME:
2416 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Aborting parking dome...";
2420 case SHUTDOWN_PARK_MOUNT:
2423 case SHUTDOWN_PARKING_MOUNT:
2424 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Aborting parking mount...";
2428 case SHUTDOWN_PARK_CAP:
2429 case SHUTDOWN_PARKING_CAP:
2432 case SHUTDOWN_COMPLETE:
2435 case SHUTDOWN_ERROR:
2439 moduleState()->setShutdownState(SHUTDOWN_IDLE);
2447 moduleState()->setupNextIteration(RUN_NOTHING);
2449 emit schedulerPaused();
2455 for (SchedulerJob * job : moduleState()->jobs())
2458 job->setCompletedCount(0);
2467 auto finished_or_aborted = [](SchedulerJob
const *
const job)
2474 auto neither_scheduled_nor_aborted = [](SchedulerJob
const *
const job)
2482 if (jobs.
isEmpty() || std::all_of(jobs.
begin(), jobs.
end(), neither_scheduled_nor_aborted))
2485 moduleState()->setActiveJob(
nullptr);
2489 else if (std::all_of(jobs.
begin(), jobs.
end(), finished_or_aborted) &&
2490 strategy != ERROR_DONT_RESTART)
2492 appendLogText(
i18n(
"Only aborted jobs left in the scheduler queue after evaluating, rescheduling those."));
2493 std::for_each(jobs.
begin(), jobs.
end(), [](SchedulerJob * job)
2495 if (SCHEDJOB_ABORTED == job->getState())
2496 job->setState(SCHEDJOB_EVALUATION);
2503 SchedulerJob *scheduledJob = getGreedyScheduler()->getScheduledJob();
2507 moduleState()->setActiveJob(
nullptr);
2510 if (activeJob() !=
nullptr && scheduledJob != activeJob())
2513 for (
auto job : m_activeJobs.values())
2515 stopCapturing(job->getOpticalTrain(),
false);
2518 m_activeJobs.clear();
2520 moduleState()->setActiveJob(scheduledJob);
2528 if (SCHEDULER_RUNNING != moduleState()->schedulerState())
2532 moduleState()->resetSequenceExecutionCounter();
2540 for (
auto job : moduleState()->jobs())
2544 if (moduleState()->jobs().isEmpty())
2547 if (Options::rememberJobProgress())
2550 moduleState()->calculateDawnDusk();
2552 getGreedyScheduler()->scheduleJobs(moduleState()->jobs(), SchedulerModuleState::getLocalTime(),
2553 moduleState()->capturedFramesCount(),
this);
2557 if (!evaluateOnly && moduleState()->schedulerState() == SCHEDULER_RUNNING)
2562 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Ekos finished evaluating jobs, no job selection required.";
2564 emit jobsUpdated(moduleState()->getJSONJobs());
2569 if (moduleState()->schedulerState() == SCHEDULER_PAUSED)
2571 if (activeJob() ==
nullptr)
2576 switch (activeJob()->getState())
2592 if (activeJob() ==
nullptr)
2595 if (moduleState()->shutdownState() == SHUTDOWN_COMPLETE
2596 || moduleState()->shutdownState() == SHUTDOWN_ERROR)
2602 if (moduleState()->shutdownState() > SHUTDOWN_IDLE)
2605 if (moduleState()->ekosState() == EKOS_STOPPING &&
checkEkosState() ==
false)
2620 if (
nullptr == activeJob() && moduleState()->checkRepeatSequence())
2629 moduleState()->increaseSequenceExecutionCounter();
2630 appendLogText(
i18n(
"Starting job sequence iteration #%1", moduleState()->sequenceExecutionCounter()));
2636 if (
nullptr == activeJob())
2646 if (moduleState()->startupState() == STARTUP_ERROR)
2654 if ((moduleState()->startupState() == STARTUP_IDLE
2656 || moduleState()->startupState() == STARTUP_SCRIPT)
2672 if (moduleState()->startupState() > STARTUP_SCRIPT
2673 && moduleState()->startupState() < STARTUP_ERROR
2685 emit updateJobTable();
2693 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Get next action...";
2695 switch (activeJob()->getStage())
2697 case SCHEDSTAGE_IDLE:
2698 if (activeJob()->getLightFramesRequired())
2700 if (activeJob()->getStepPipeline() & SchedulerJob::USE_TRACK)
2702 else if (activeJob()->getStepPipeline() & SchedulerJob::USE_FOCUS && moduleState()->autofocusCompleted() ==
false)
2704 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"startFocusing on 3485";
2707 else if (activeJob()->getStepPipeline() & SchedulerJob::USE_ALIGN)
2709 else if (activeJob()->getStepPipeline() & SchedulerJob::USE_GUIDE)
2722 if (activeJob()->getStepPipeline())
2724 i18n(
"Job '%1' is proceeding directly to capture stage because only calibration frames are pending.",
2725 activeJob()->getName()));
2731 case SCHEDSTAGE_SLEW_COMPLETE:
2732 if (activeJob()->getStepPipeline() & SchedulerJob::USE_FOCUS && moduleState()->autofocusCompleted() ==
false)
2734 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"startFocusing on 3514";
2737 else if (activeJob()->getStepPipeline() & SchedulerJob::USE_ALIGN)
2739 else if (activeJob()->getStepPipeline() & SchedulerJob::USE_GUIDE)
2745 case SCHEDSTAGE_FOCUS_COMPLETE:
2746 if (activeJob()->getStepPipeline() & SchedulerJob::USE_ALIGN)
2748 else if (activeJob()->getStepPipeline() & SchedulerJob::USE_GUIDE)
2754 case SCHEDSTAGE_ALIGN_COMPLETE:
2755 moduleState()->updateJobStage(SCHEDSTAGE_RESLEWING);
2758 case SCHEDSTAGE_RESLEWING_COMPLETE:
2761 if ((activeJob()->getStepPipeline() & SchedulerJob::USE_FOCUS) && activeJob()->getInSequenceFocus())
2764 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"startFocusing on 3544";
2767 else if (activeJob()->getStepPipeline() & SchedulerJob::USE_GUIDE)
2773 case SCHEDSTAGE_POSTALIGN_FOCUSING_COMPLETE:
2774 if (activeJob()->getStepPipeline() & SchedulerJob::USE_GUIDE)
2780 case SCHEDSTAGE_GUIDING_COMPLETE:
2798 constexpr int oneHour = 1000 * 3600;
2799 moduleState()->tickleTimer().stop();
2801 if (msSleep > oneHour)
2805 if (moduleState() && moduleState()->currentlySleeping())
2807 moduleState()->tickleTimer().start(oneHour);
2808 emit updateJobTable(
nullptr);
2811 moduleState()->tickleTimer().setSingleShot(
true);
2812 moduleState()->tickleTimer().start(oneHour);
2814 moduleState()->iterationTimer().setSingleShot(
true);
2815 moduleState()->iterationTimer().start(msSleep);
2822 if (moduleState()->startMSecs() == 0)
2823 moduleState()->setStartMSecs(now);
2836 moduleState()->setIterationSetup(
false);
2837 switch (keepTimerState)
2840 changeSleepLabel(
"",
false);
2853 moduleState()->setTimerInterval(-1);
2856 if (!moduleState()->iterationSetup())
2862 moduleState()->setTimerInterval(moduleState()->updatePeriodMs());
2865 return moduleState()->timerInterval();
2870 Q_ASSERT_X(activeJob(), __FUNCTION__,
"Actual current job is required to check job stage");
2874 if (checkJobStageCounter == 0)
2876 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Checking job stage for" << activeJob()->getName() <<
"startup" <<
2877 activeJob()->getStartupCondition() << activeJob()->getStartupTime().toString() <<
"state" << activeJob()->getState();
2878 if (checkJobStageCounter++ == 30)
2879 checkJobStageCounter = 0;
2882 emit syncGreedyParams();
2883 if (!getGreedyScheduler()->checkJob(moduleState()->leadJobs(), SchedulerModuleState::getLocalTime(), activeJob()))
2890 checkJobStageEpilogue();
2893void SchedulerProcess::checkJobStageEpilogue()
2908 if (!activeJob())
return;
2909 switch (activeJob()->getStage())
2911 case SCHEDSTAGE_IDLE:
2913 emit jobStarted(activeJob()->getName());
2917 case SCHEDSTAGE_ALIGNING:
2919 if (moduleState()->getCurrentOperationMsec() >
static_cast<int>(ALIGN_INACTIVITY_TIMEOUT))
2921 QVariant const status = alignInterface()->property(
"status");
2926 if (moduleState()->increaseAlignFailureCount())
2928 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Align module timed out. Restarting request...";
2933 appendLogText(
i18n(
"Warning: job '%1' alignment procedure failed, marking aborted.", activeJob()->getName()));
2939 moduleState()->startCurrentOperationTimer();
2943 case SCHEDSTAGE_CAPTURING:
2945 if (moduleState()->getCurrentOperationMsec() >
static_cast<int>(CAPTURE_INACTIVITY_TIMEOUT))
2947 QVariant
const status = captureInterface()->property(
"status");
2952 if (moduleState()->increaseCaptureFailureCount())
2954 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"capture module timed out. Restarting request...";
2959 appendLogText(
i18n(
"Warning: job '%1' capture procedure failed, marking aborted.", activeJob()->getName()));
2964 else moduleState()->startCurrentOperationTimer();
2968 case SCHEDSTAGE_FOCUSING:
2970 if (moduleState()->getCurrentOperationMsec() >
static_cast<int>(FOCUS_INACTIVITY_TIMEOUT))
2972 bool success =
true;
2973 foreach (
const QString trainname, m_activeJobs.keys())
2975 QList<QVariant> dbusargs;
2976 dbusargs.
append(trainname);
2977 QDBusReply<Ekos::FocusState> statusReply = focusInterface()->callWithArgumentList(
QDBus::AutoDetect,
"status", dbusargs);
2980 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: job '%1' status request received DBUS error: %2").arg(
2990 Ekos::FocusState focusStatus = statusReply.value();
2991 if (focusStatus == Ekos::FOCUS_IDLE || focusStatus == Ekos::FOCUS_WAITING)
2993 if (moduleState()->increaseFocusFailureCount(trainname))
2995 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Focus module timed out. Restarting request...";
3003 if (success ==
false)
3005 appendLogText(
i18n(
"Warning: job '%1' focusing procedure failed, marking aborted.", activeJob()->getName()));
3010 else moduleState()->startCurrentOperationTimer();
3013 case SCHEDSTAGE_GUIDING:
3015 if (moduleState()->getCurrentOperationMsec() > GUIDE_INACTIVITY_TIMEOUT)
3019 if (guideStatus == Ekos::GUIDE_IDLE || guideStatus == Ekos::GUIDE_CONNECTED || guideStatus == Ekos::GUIDE_DISCONNECTED)
3021 if (moduleState()->increaseGuideFailureCount())
3023 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"guide module timed out. Restarting request...";
3028 appendLogText(
i18n(
"Warning: job '%1' guiding procedure failed, marking aborted.", activeJob()->getName()));
3033 else moduleState()->startCurrentOperationTimer();
3037 case SCHEDSTAGE_SLEWING:
3038 case SCHEDSTAGE_RESLEWING:
3041 QVariant
const slewStatus = mountInterface()->property(
"status");
3047 ISD::Mount::Status
const status =
static_cast<ISD::Mount::Status
>(slewStatus.
toInt());
3048 setMountStatus(status);
3052 appendLogText(
i18n(
"Warning: job '%1' lost connection to the mount, attempting to reconnect.", activeJob()->getName()));
3060 case SCHEDSTAGE_SLEW_COMPLETE:
3061 case SCHEDSTAGE_RESLEWING_COMPLETE:
3063 if (moduleState()->domeReady())
3065 QVariant
const isDomeMoving = domeInterface()->property(
"isMoving");
3069 appendLogText(
i18n(
"Warning: job '%1' lost connection to the dome, attempting to reconnect.", activeJob()->getName()));
3075 if (!isDomeMoving.
value<
bool>())
3088 moduleState()->calculateDawnDusk();
3090 if (SCHEDULER_RUNNING != moduleState()->schedulerState())
3102 if (activeJob() == job &&
SCHEDJOB_BUSY == activeJob()->getState())
3105 moduleState()->setActiveJob(job);
3117 else if (0 < SchedulerModuleState::getLocalTime().secsTo(activeJob()->getStartupTime()))
3122 if (job->getCompletionCondition() == FINISH_SEQUENCE && Options::rememberJobProgress())
3123 captureInterface()->setProperty(
"targetName", job->getName());
3125 moduleState()->calculateDawnDusk();
3129 moduleState()->setAutofocusCompleted(job->getOpticalTrain(),
false);
3131 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Executing Job " << activeJob()->getName();
3134 emit jobsUpdated(moduleState()->getJSONJobs());
3136 KSNotification::event(
QLatin1String(
"EkosSchedulerJobStart"),
3137 i18n(
"Ekos job started (%1)", activeJob()->getName()), KSNotification::Scheduler);
3140 moduleState()->setupNextIteration(RUN_JOBCHECK);
3152 KSNotification::sorry(message,
i18n(
"Could Not Open File"));
3161 outstream <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" <<
Qt::endl;
3162 outstream <<
"<SchedulerList version='2.1'>" <<
Qt::endl;
3164 outstream <<
"<Profile>" <<
QString(entityXML(strdup(moduleState()->currentProfile().toStdString().c_str()))) <<
3167 auto tiles = KStarsData::Instance()->skyComposite()->mosaicComponent()->tiles();
3168 bool useMosaicInfo = !tiles->sequenceFile().isEmpty();
3172 outstream <<
"<Mosaic>" <<
Qt::endl;
3173 outstream <<
"<Target>" << tiles->targetName() <<
"</Target>" <<
Qt::endl;
3174 outstream <<
"<Group>" << tiles->group() <<
"</Group>" <<
Qt::endl;
3176 QString ccArg, ccValue = tiles->completionCondition(&ccArg);
3177 if (ccValue ==
"FinishSequence")
3178 outstream <<
"<FinishSequence/>" <<
Qt::endl;
3179 else if (ccValue ==
"FinishLoop")
3180 outstream <<
"<FinishLoop/>" <<
Qt::endl;
3181 else if (ccValue ==
"FinishRepeat")
3182 outstream <<
"<FinishRepeat>" << ccArg <<
"</FinishRepeat>" <<
Qt::endl;
3184 outstream <<
"<Sequence>" << tiles->sequenceFile() <<
"</Sequence>" <<
Qt::endl;
3185 outstream <<
"<Directory>" << tiles->outputDirectory() <<
"</Directory>" <<
Qt::endl;
3187 outstream <<
"<FocusEveryN>" << tiles->focusEveryN() <<
"</FocusEveryN>" <<
Qt::endl;
3188 outstream <<
"<AlignEveryN>" << tiles->alignEveryN() <<
"</AlignEveryN>" <<
Qt::endl;
3189 if (tiles->isTrackChecked())
3190 outstream <<
"<TrackChecked/>" <<
Qt::endl;
3191 if (tiles->isFocusChecked())
3192 outstream <<
"<FocusChecked/>" <<
Qt::endl;
3193 if (tiles->isAlignChecked())
3194 outstream <<
"<AlignChecked/>" <<
Qt::endl;
3195 if (tiles->isGuideChecked())
3196 outstream <<
"<GuideChecked/>" <<
Qt::endl;
3197 outstream <<
"<Overlap>" << cLocale.
toString(tiles->overlap()) <<
"</Overlap>" <<
Qt::endl;
3198 outstream <<
"<CenterRA>" << cLocale.
toString(tiles->ra0().Hours()) <<
"</CenterRA>" <<
Qt::endl;
3199 outstream <<
"<CenterDE>" << cLocale.
toString(tiles->dec0().Degrees()) <<
"</CenterDE>" <<
Qt::endl;
3200 outstream <<
"<GridW>" << tiles->gridSize().width() <<
"</GridW>" <<
Qt::endl;
3201 outstream <<
"<GridH>" << tiles->gridSize().height() <<
"</GridH>" <<
Qt::endl;
3202 outstream <<
"<FOVW>" << cLocale.
toString(tiles->mosaicFOV().width()) <<
"</FOVW>" <<
Qt::endl;
3203 outstream <<
"<FOVH>" << cLocale.
toString(tiles->mosaicFOV().height()) <<
"</FOVH>" <<
Qt::endl;
3204 outstream <<
"<CameraFOVW>" << cLocale.
toString(tiles->cameraFOV().width()) <<
"</CameraFOVW>" <<
Qt::endl;
3205 outstream <<
"<CameraFOVH>" << cLocale.
toString(tiles->cameraFOV().height()) <<
"</CameraFOVH>" <<
Qt::endl;
3206 outstream <<
"</Mosaic>" <<
Qt::endl;
3210 for (
auto &job : moduleState()->jobs())
3215 outstream <<
"<JobType lead='" << (job->isLead() ?
"true" :
"false") <<
"'/>" <<
Qt::endl;
3220 outstream <<
"<Coordinates>" <<
Qt::endl;
3223 outstream <<
"</Coordinates>" <<
Qt::endl;
3226 if (! job->getOpticalTrain().
isEmpty())
3227 outstream <<
"<OpticalTrain>" <<
QString(entityXML(strdup(job->getOpticalTrain().
toStdString().c_str()))) <<
3230 if (job->isLead() && job->getFITSFile().
isValid() && job->getFITSFile().
isEmpty() ==
false)
3233 outstream <<
"<PositionAngle>" << job->getPositionAngle() <<
"</PositionAngle>" <<
Qt::endl;
3235 outstream <<
"<Sequence>" << job->getSequenceFile().
toLocalFile() <<
"</Sequence>" <<
Qt::endl;
3237 if (useMosaicInfo && index < tiles->tiles().size())
3239 auto oneTile = tiles->tiles().at(index++);
3240 outstream <<
"<TileCenter>" <<
Qt::endl;
3241 outstream <<
"<X>" << cLocale.
toString(oneTile->center.x()) <<
"</X>" <<
Qt::endl;
3242 outstream <<
"<Y>" << cLocale.
toString(oneTile->center.y()) <<
"</Y>" <<
Qt::endl;
3243 outstream <<
"<Rotation>" << cLocale.
toString(oneTile->rotation) <<
"</Rotation>" <<
Qt::endl;
3244 outstream <<
"</TileCenter>" <<
Qt::endl;
3249 outstream <<
"<StartupCondition>" <<
Qt::endl;
3250 if (job->getFileStartupCondition() == START_ASAP)
3251 outstream <<
"<Condition>ASAP</Condition>" <<
Qt::endl;
3252 else if (job->getFileStartupCondition() == START_AT)
3253 outstream <<
"<Condition value='" << job->getStartAtTime().
toString(
Qt::ISODate) <<
"'>At</Condition>"
3255 outstream <<
"</StartupCondition>" <<
Qt::endl;
3257 outstream <<
"<Constraints>" <<
Qt::endl;
3258 if (job->hasMinAltitude())
3259 outstream <<
"<Constraint value='" << cLocale.
toString(job->getMinAltitude()) <<
"'>MinimumAltitude</Constraint>" <<
3261 if (job->getMinMoonSeparation() > 0)
3262 outstream <<
"<Constraint value='" << cLocale.
toString(job->getMinMoonSeparation()) <<
"'>MoonSeparation</Constraint>"
3264 if (job->getMaxMoonAltitude() < 90)
3265 outstream <<
"<Constraint value='" << cLocale.
toString(job->getMaxMoonAltitude()) <<
"'>MoonMaxAltitude</Constraint>"
3267 if (job->getEnforceTwilight())
3268 outstream <<
"<Constraint>EnforceTwilight</Constraint>" <<
Qt::endl;
3269 if (job->getEnforceArtificialHorizon())
3270 outstream <<
"<Constraint>EnforceArtificialHorizon</Constraint>" <<
Qt::endl;
3271 outstream <<
"</Constraints>" <<
Qt::endl;
3274 outstream <<
"<CompletionCondition>" <<
Qt::endl;
3275 if (job->getCompletionCondition() == FINISH_SEQUENCE)
3276 outstream <<
"<Condition>Sequence</Condition>" <<
Qt::endl;
3277 else if (job->getCompletionCondition() == FINISH_REPEAT)
3278 outstream <<
"<Condition value='" << cLocale.
toString(job->getRepeatsRequired()) <<
"'>Repeat</Condition>" <<
Qt::endl;
3279 else if (job->getCompletionCondition() == FINISH_LOOP)
3280 outstream <<
"<Condition>Loop</Condition>" <<
Qt::endl;
3281 else if (job->getCompletionCondition() == FINISH_AT)
3282 outstream <<
"<Condition value='" << job->getFinishAtTime().
toString(
Qt::ISODate) <<
"'>At</Condition>"
3284 outstream <<
"</CompletionCondition>" <<
Qt::endl;
3288 outstream <<
"<Steps>" <<
Qt::endl;
3289 if (job->getStepPipeline() & SchedulerJob::USE_TRACK)
3290 outstream <<
"<Step>Track</Step>" <<
Qt::endl;
3291 if (job->getStepPipeline() & SchedulerJob::USE_FOCUS)
3292 outstream <<
"<Step>Focus</Step>" <<
Qt::endl;
3293 if (job->getStepPipeline() & SchedulerJob::USE_ALIGN)
3294 outstream <<
"<Step>Align</Step>" <<
Qt::endl;
3295 if (job->getStepPipeline() & SchedulerJob::USE_GUIDE)
3296 outstream <<
"<Step>Guide</Step>" <<
Qt::endl;
3297 outstream <<
"</Steps>" <<
Qt::endl;
3302 outstream <<
"<SchedulerAlgorithm value='" << ALGORITHM_GREEDY <<
"'/>" <<
Qt::endl;
3303 outstream <<
"<ErrorHandlingStrategy value='" << Options::errorHandlingStrategy() <<
"'>" <<
Qt::endl;
3304 if (Options::rescheduleErrors())
3305 outstream <<
"<RescheduleErrors />" <<
Qt::endl;
3306 outstream <<
"<delay>" << Options::errorHandlingStrategyDelay() <<
"</delay>" <<
Qt::endl;
3307 outstream <<
"</ErrorHandlingStrategy>" <<
Qt::endl;
3309 outstream <<
"<StartupProcedure>" <<
Qt::endl;
3310 if (moduleState()->startupScriptURL().isEmpty() ==
false)
3311 outstream <<
"<Procedure value='" << moduleState()->startupScriptURL().toString(
QUrl::PreferLocalFile) <<
3312 "'>StartupScript</Procedure>" <<
Qt::endl;
3313 if (Options::schedulerUnparkDome())
3314 outstream <<
"<Procedure>UnparkDome</Procedure>" <<
Qt::endl;
3315 if (Options::schedulerUnparkMount())
3316 outstream <<
"<Procedure>UnparkMount</Procedure>" <<
Qt::endl;
3317 if (Options::schedulerOpenDustCover())
3318 outstream <<
"<Procedure>UnparkCap</Procedure>" <<
Qt::endl;
3319 outstream <<
"</StartupProcedure>" <<
Qt::endl;
3321 outstream <<
"<ShutdownProcedure>" <<
Qt::endl;
3322 if (Options::schedulerWarmCCD())
3323 outstream <<
"<Procedure>WarmCCD</Procedure>" <<
Qt::endl;
3324 if (Options::schedulerCloseDustCover())
3325 outstream <<
"<Procedure>ParkCap</Procedure>" <<
Qt::endl;
3326 if (Options::schedulerParkMount())
3327 outstream <<
"<Procedure>ParkMount</Procedure>" <<
Qt::endl;
3328 if (Options::schedulerParkDome())
3329 outstream <<
"<Procedure>ParkDome</Procedure>" <<
Qt::endl;
3330 if (moduleState()->shutdownScriptURL().isEmpty() ==
false)
3331 outstream <<
"<Procedure value='" << moduleState()->shutdownScriptURL().toString(
QUrl::PreferLocalFile) <<
3332 "'>schedulerStartupScript</Procedure>" <<
3334 outstream <<
"</ShutdownProcedure>" <<
Qt::endl;
3336 outstream <<
"</SchedulerList>" <<
Qt::endl;
3340 moduleState()->setDirty(
false);
3344void SchedulerProcess::checkAlignment(
const QVariantMap &metadata,
const QString &trainname)
3347 if (activeJob() ==
nullptr || (activeJob()->getOpticalTrain() !=
"" && activeJob()->getOpticalTrain() != trainname))
3349 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Ignoring metadata from train =" << trainname <<
"for alignment check.";
3353 if (activeJob()->getStepPipeline() & SchedulerJob::USE_ALIGN &&
3354 metadata[
"type"].toInt() == FRAME_LIGHT &&
3355 Options::alignCheckFrequency() > 0 &&
3356 moduleState()->increaseSolverIteration() >= Options::alignCheckFrequency())
3358 moduleState()->resetSolverIteration();
3360 auto filename = metadata[
"filename"].toString();
3361 auto exposure = metadata[
"exposure"].toDouble();
3363 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Checking alignment on train =" << trainname <<
"for" << filename;
3365 constexpr double minSolverSeconds = 5.0;
3366 double solverTimeout = std::max(exposure - 2, minSolverSeconds);
3367 if (solverTimeout >= minSolverSeconds)
3369 auto profiles = getDefaultAlignOptionsProfiles();
3371 SSolver::Parameters parameters;
3376 parameters = profiles.at(Options::solveOptionsProfile());
3378 catch (std::out_of_range
const &)
3380 parameters = profiles[0];
3384 parameters.search_radius = parameters.search_radius * 2;
3389 auto width = metadata[
"width"].toUInt() / (metadata[
"binx"].isValid() ? metadata[
"binx"].toUInt() : 1);
3390 auto height = metadata[
"height"].toUInt() / (metadata[
"biny"].isValid() ? metadata[
"biny"].toUInt() : 1);
3392 auto lowScale = Options::astrometryImageScaleLow();
3393 auto highScale = Options::astrometryImageScaleHigh();
3396 if (Options::astrometryImageScaleUnits() == SSolver::DEG_WIDTH)
3398 lowScale = (lowScale * 3600) / std::max(width, height);
3399 highScale = (highScale * 3600) / std::min(width, height);
3401 else if (Options::astrometryImageScaleUnits() == SSolver::ARCMIN_WIDTH)
3403 lowScale = (lowScale * 60) / std::max(width, height);
3404 highScale = (highScale * 60) / std::min(width, height);
3407 m_Solver->useScale(Options::astrometryUseImageScale(), lowScale, highScale);
3408 m_Solver->usePosition(Options::astrometryUsePosition(), activeJob()->getTargetCoords().ra().Degrees(),
3409 activeJob()->getTargetCoords().
dec().Degrees());
3410 m_Solver->setHealpix(moduleState()->indexToUse(), moduleState()->healpixToUse());
3411 m_Solver->runSolver(filename);
3416void SchedulerProcess::solverDone(
bool timedOut,
bool success,
const FITSImage::Solution &solution,
double elapsedSeconds)
3418 disconnect(m_Solver.get(), &SolverUtils::done,
this, &Ekos::SchedulerProcess::solverDone);
3423 QString healpixString =
"";
3424 if (moduleState()->indexToUse() != -1 || moduleState()->healpixToUse() != -1)
3425 healpixString = QString(
"Healpix %1 Index %2").
arg(moduleState()->healpixToUse()).
arg(moduleState()->indexToUse());
3427 if (timedOut || !success)
3430 moduleState()->setIndexToUse(-1);
3431 moduleState()->setHealpixToUse(-1);
3437 m_Solver->getSolutionHealpix(&index, &healpix);
3438 moduleState()->setIndexToUse(index);
3439 moduleState()->setHealpixToUse(healpix);
3443 appendLogText(
i18n(
"Solver timed out: %1s %2", QString(
"%L1").arg(elapsedSeconds, 0,
'f', 1), healpixString));
3445 appendLogText(
i18n(
"Solver failed: %1s %2", QString(
"%L1").arg(elapsedSeconds, 0,
'f', 1), healpixString));
3448 const double ra = solution.ra;
3449 const double dec = solution.dec;
3451 const auto target = activeJob()->getTargetCoords();
3453 SkyPoint alignCoord;
3454 alignCoord.
setRA0(ra / 15.0);
3458 const double diffRa = (alignCoord.
ra().deltaAngle(target.ra())).Degrees() * 3600;
3459 const double diffDec = (alignCoord.
dec().deltaAngle(target.dec())).Degrees() * 3600;
3462 const double diffTotal = hypot(diffRa, diffDec);
3466 qCDebug(KSTARS_EKOS_SCHEDULER) <<
3467 QString(
"Target Distance: %1\" Target (RA: %2 DE: %3) Current (RA: %4 DE: %5) %6 solved in %7s")
3468 .arg(QString(
"%L1").arg(diffTotal, 0,
'f', 0),
3469 target.ra().toDMSString(),
3470 target.dec().toDMSString(),
3474 QString(
"%L1").arg(elapsedSeconds, 0,
'f', 2));
3475 emit targetDistance(diffTotal);
3478 if (diffTotal / 60 > Options::alignCheckThreshold())
3490 SchedulerState
const old_state = moduleState()->schedulerState();
3491 moduleState()->setSchedulerState(SCHEDULER_LOADING);
3498 QString message =
i18n(
"Unable to open file %1", fileURL);
3499 KSNotification::sorry(message,
i18n(
"Could Not Open File"));
3500 moduleState()->setSchedulerState(old_state);
3504 LilXML *xmlParser = newLilXML();
3505 char errmsg[MAXRBUF];
3506 XMLEle *root =
nullptr;
3507 XMLEle *ep =
nullptr;
3508 XMLEle *subEP =
nullptr;
3515 SchedulerJob *lastLead =
nullptr;
3518 const QStringList allTrainNames = OpticalTrainManager::Instance()->getTrainNames();
3523 root = readXMLEle(xmlParser, c, errmsg);
3527 for (ep = nextXMLEle(root, 1); ep !=
nullptr; ep = nextXMLEle(root, 0))
3529 const char *tag = tagXMLEle(ep);
3530 if (!strcmp(tag,
"Job"))
3532 SchedulerJob *newJob = SchedulerUtils::createJob(ep, lastLead);
3534 if (newJob->isLead())
3538 remainingTrainNames = allTrainNames;
3541 const QString trainname = newJob->getOpticalTrain();
3542 bool allowedName = (newJob->isLead() && trainname.
isEmpty()) || allTrainNames.
contains(trainname);
3543 bool availableName = (newJob->isLead() && trainname.
isEmpty()) || !remainingTrainNames.
isEmpty();
3545 if (!allowedName && availableName)
3548 i18n(
"Warning: train name is empty, selecting \"%1\".", remainingTrainNames.
first()) :
3549 i18n(
"Warning: train name %2 does not exist, selecting \"%1\".", remainingTrainNames.
first(), trainname);
3555 newJob->setOpticalTrain(remainingTrainNames.
first());
3558 else if (!availableName)
3560 const QString message =
i18n(
"Warning: no available train name for scheduler job, select the optical train name manually.");
3568 emit addJob(newJob);
3570 else if (!strcmp(tag,
"Mosaic"))
3573 auto tiles = KStarsData::Instance()->skyComposite()->mosaicComponent()->tiles();
3574 tiles->fromXML(fileURL);
3576 else if (!strcmp(tag,
"Profile"))
3578 moduleState()->setCurrentProfile(pcdataXMLEle(ep));
3581 else if (!strcmp(tag,
"SchedulerAlgorithm"))
3583 int algIndex = cLocale.
toInt(findXMLAttValu(ep,
"value"));
3584 if (algIndex != ALGORITHM_GREEDY)
3585 appendLogText(
i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
3587 else if (!strcmp(tag,
"ErrorHandlingStrategy"))
3592 subEP = findXMLEle(ep,
"delay");
3595 Options::setErrorHandlingStrategyDelay(cLocale.
toInt(pcdataXMLEle(subEP)));
3597 subEP = findXMLEle(ep,
"RescheduleErrors");
3598 Options::setRescheduleErrors(subEP !=
nullptr);
3600 else if (!strcmp(tag,
"StartupProcedure"))
3603 Options::setSchedulerUnparkDome(
false);
3604 Options::setSchedulerUnparkMount(
false);
3605 Options::setSchedulerOpenDustCover(
false);
3607 for (procedure = nextXMLEle(ep, 1); procedure !=
nullptr; procedure = nextXMLEle(ep, 0))
3609 const char *proc = pcdataXMLEle(procedure);
3611 if (!strcmp(proc,
"StartupScript"))
3613 moduleState()->setStartupScriptURL(
QUrl::fromUserInput(findXMLAttValu(procedure,
"value")));
3615 else if (!strcmp(proc,
"UnparkDome"))
3616 Options::setSchedulerUnparkDome(
true);
3617 else if (!strcmp(proc,
"UnparkMount"))
3618 Options::setSchedulerUnparkMount(
true);
3619 else if (!strcmp(proc,
"UnparkCap"))
3620 Options::setSchedulerOpenDustCover(
true);
3623 else if (!strcmp(tag,
"ShutdownProcedure"))
3626 Options::setSchedulerWarmCCD(
false);
3627 Options::setSchedulerParkDome(
false);
3628 Options::setSchedulerParkMount(
false);
3629 Options::setSchedulerCloseDustCover(
false);
3631 for (procedure = nextXMLEle(ep, 1); procedure !=
nullptr; procedure = nextXMLEle(ep, 0))
3633 const char *proc = pcdataXMLEle(procedure);
3635 if (!strcmp(proc,
"ShutdownScript"))
3637 moduleState()->setShutdownScriptURL(
QUrl::fromUserInput(findXMLAttValu(procedure,
"value")));
3639 else if (!strcmp(proc,
"WarmCCD"))
3640 Options::setSchedulerWarmCCD(
true);
3641 else if (!strcmp(proc,
"ParkDome"))
3642 Options::setSchedulerParkDome(
true);
3643 else if (!strcmp(proc,
"ParkMount"))
3644 Options::setSchedulerParkMount(
true);
3645 else if (!strcmp(proc,
"ParkCap"))
3646 Options::setSchedulerCloseDustCover(
true);
3651 emit syncGUIToGeneralSettings();
3656 delLilXML(xmlParser);
3657 moduleState()->setSchedulerState(old_state);
3662 moduleState()->setDirty(
false);
3663 delLilXML(xmlParser);
3664 emit updateSchedulerURL(fileURL);
3666 moduleState()->setSchedulerState(old_state);
3676 int const max_log_count = 2000;
3677 if (moduleState()->logText().size() > max_log_count)
3678 moduleState()->logText().removeLast();
3680 moduleState()->logText().prepend(
i18nc(
"log entry; %1 is the date, %2 is the text",
"%1 %2",
3681 SchedulerModuleState::getLocalTime().toString(
"yyyy-MM-ddThh:mm:ss"), logentry));
3683 qCInfo(KSTARS_EKOS_SCHEDULER) << logentry;
3685 emit newLog(logentry);
3690 moduleState()->logText().clear();
3694void SchedulerProcess::setAlignStatus(
AlignState status)
3696 if (moduleState()->schedulerState() == SCHEDULER_PAUSED || activeJob() ==
nullptr)
3699 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Align State" << Ekos::getAlignStatusString(status);
3704 QDateTime const now = SchedulerModuleState::getLocalTime();
3705 if (now < activeJob()->getStartupTime())
3709 if (activeJob()->getStage() == SCHEDSTAGE_ALIGNING)
3715 moduleState()->resetAlignFailureCount();
3717 moduleState()->updateJobStage(SCHEDSTAGE_ALIGN_COMPLETE);
3720 if (activeJob()->getFITSFile().isEmpty() ==
false)
3726 activeJob()->setTargetCoords(
dms(values[0] * 15.0),
dms(values[1]), KStarsData::Instance()->ut().djd());
3733 appendLogText(
i18n(
"Warning: job '%1' alignment failed.", activeJob()->getName()));
3735 if (moduleState()->increaseAlignFailureCount())
3737 if (Options::resetMountModelOnAlignFail() && moduleState()->maxFailureAttempts() - 1 < moduleState()->alignFailureCount())
3739 appendLogText(
i18n(
"Warning: job '%1' forcing mount model reset after failing alignment #%2.", activeJob()->getName(),
3740 moduleState()->alignFailureCount()));
3743 appendLogText(
i18n(
"Restarting %1 alignment procedure...", activeJob()->getName()));
3748 appendLogText(
i18n(
"Warning: job '%1' alignment procedure failed, marking aborted.", activeJob()->getName()));
3757void SchedulerProcess::setGuideStatus(GuideState status)
3759 if (moduleState()->schedulerState() == SCHEDULER_PAUSED || activeJob() ==
nullptr)
3762 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Guide State" << Ekos::getGuideStatusString(status);
3767 QDateTime
const now = SchedulerModuleState::getLocalTime();
3768 if (now < activeJob()->getStartupTime())
3772 if (activeJob()->getStage() == SCHEDSTAGE_GUIDING)
3774 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Calibration & Guide stage...";
3777 if (status == Ekos::GUIDE_GUIDING)
3779 appendLogText(
i18n(
"Job '%1' guiding is in progress.", activeJob()->getName()));
3780 moduleState()->resetGuideFailureCount();
3782 moduleState()->cancelGuidingTimer();
3784 moduleState()->updateJobStage(SCHEDSTAGE_GUIDING_COMPLETE);
3787 else if (status == Ekos::GUIDE_CALIBRATION_ERROR ||
3788 status == Ekos::GUIDE_ABORTED)
3790 if (status == Ekos::GUIDE_ABORTED)
3791 appendLogText(
i18n(
"Warning: job '%1' guiding failed.", activeJob()->getName()));
3793 appendLogText(
i18n(
"Warning: job '%1' calibration failed.", activeJob()->getName()));
3799 if (moduleState()->isGuidingTimerActive())
3802 if (moduleState()->increaseGuideFailureCount())
3804 if (status == Ekos::GUIDE_CALIBRATION_ERROR &&
3805 Options::realignAfterCalibrationFailure())
3807 appendLogText(
i18n(
"Restarting %1 alignment procedure...", activeJob()->getName()));
3812 appendLogText(
i18n(
"Job '%1' is guiding, guiding procedure will be restarted in %2 seconds.", activeJob()->getName(),
3813 (RESTART_GUIDING_DELAY_MS * moduleState()->guideFailureCount()) / 1000));
3814 moduleState()->startGuidingTimer(RESTART_GUIDING_DELAY_MS * moduleState()->guideFailureCount());
3819 appendLogText(
i18n(
"Warning: job '%1' guiding procedure failed, marking aborted.", activeJob()->getName()));
3828void SchedulerProcess::setCaptureStatus(
CaptureState status,
const QString &trainname)
3830 if (activeJob() ==
nullptr || !m_activeJobs.contains(trainname))
3833 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Capture State" << Ekos::getCaptureStatusString(status) <<
"train =" << trainname;
3835 SchedulerJob *job = m_activeJobs[trainname];
3840 QDateTime
const now = SchedulerModuleState::getLocalTime();
3841 if (now < job->getStartupTime())
3845 if (job->getStage() == SCHEDSTAGE_CAPTURING)
3857 const SkyPoint targetCoords = activeJob()->getTargetCoords();
3858 QList<QVariant> targetArgs;
3860 alignInterface()->callWithArgumentList(
QDBus::AutoDetect,
"setTargetCoords", targetArgs);
3865 appendLogText(
i18n(
"[%2] Warning: job '%1' failed to capture target.", job->getName(), trainname));
3870 if (moduleState()->increaseCaptureFailureCount())
3875 if (activeJob()->getStepPipeline() & SchedulerJob::USE_GUIDE)
3879 if (gStatus == Ekos::GUIDE_ABORTED ||
3880 gStatus == Ekos::GUIDE_CALIBRATION_ERROR ||
3881 gStatus == GUIDE_DITHERING_ERROR)
3883 appendLogText(
i18n(
"[%2] Job '%1' is capturing, is restarting its guiding procedure (attempt #%3 of %4).",
3884 activeJob()->getName(), trainname,
3885 moduleState()->captureFailureCount(), moduleState()->maxFailureAttempts()));
3892 appendLogText(
i18n(
"Warning: job '%1' failed its capture procedure, restarting capture.", activeJob()->getName()));
3898 appendLogText(
i18n(
"[%2] Warning: job '%1' failed its capture procedure, marking aborted.", job->getName(), trainname));
3901 stopCapturing(
"",
true);
3908 if (job->leadJob()->getStage() == SCHEDSTAGE_CAPTURING)
3911 appendLogText(
i18n(
"[%2] Follower job '%1' has been aborted, is restarting.", job->getName(), trainname));
3913 startSingleCapture(job,
true);
3917 appendLogText(
i18n(
"[%2] Follower job '%1' has been aborted.", job->getName(), trainname));
3924 KSNotification::event(QLatin1String(
"EkosScheduledImagingFinished"),
3925 i18n(
"[%2] Job (%1) - Capture finished", job->getName(), trainname), KSNotification::Scheduler);
3937 if (job->getCompletionCondition() == FINISH_LOOP ||
3938 (job->getCompletionCondition() == FINISH_REPEAT && job->getRepeatsRemaining() > 0))
3941 startSingleCapture(job,
false);
3947 job->setStage(SCHEDSTAGE_COMPLETE);
3955 if (Options::rememberJobProgress())
3959 for (
const auto &job : moduleState()->jobs())
3960 SchedulerUtils::estimateJobTime(job, moduleState()->capturedFramesCount(),
this);
3964 activeJob()->setCompletedCount(job->getCompletedCount() + 1);
3968 moduleState()->resetCaptureFailureCount();
3973void SchedulerProcess::setFocusStatus(FocusState status,
const QString &trainname)
3975 if (moduleState()->schedulerState() == SCHEDULER_PAUSED || activeJob() ==
nullptr)
3978 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Train " << trainname <<
"focus state" << Ekos::getFocusStatusString(status);
3981 if (m_activeJobs.contains(trainname) ==
false)
3984 SchedulerJob *currentJob = m_activeJobs[trainname];
3989 QDateTime
const now = SchedulerModuleState::getLocalTime();
3990 if (now < activeJob()->getStartupTime())
3994 if (activeJob()->getStage() == SCHEDSTAGE_FOCUSING)
3997 if (status == Ekos::FOCUS_COMPLETE)
3999 appendLogText(
i18n(
"Job '%1' focusing train '%2' is complete.", currentJob->getName(), trainname));
4001 moduleState()->setAutofocusCompleted(trainname,
true);
4003 if (moduleState()->autofocusCompleted())
4005 moduleState()->updateJobStage(SCHEDSTAGE_FOCUS_COMPLETE);
4009 else if (status == Ekos::FOCUS_FAILED || status == Ekos::FOCUS_ABORTED)
4011 appendLogText(
i18n(
"Warning: job '%1' focusing failed.", currentJob->getName()));
4013 if (moduleState()->increaseFocusFailureCount(trainname))
4015 appendLogText(
i18n(
"Job '%1' for train '%2' is restarting its focusing procedure.", currentJob->getName(), trainname));
4020 appendLogText(
i18n(
"Warning: job '%1' on train '%2' focusing procedure failed, marking aborted.", activeJob()->getName(),
4030void SchedulerProcess::setMountStatus(ISD::Mount::Status status)
4032 if (moduleState()->schedulerState() == SCHEDULER_PAUSED || activeJob() ==
nullptr)
4035 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Mount State changed to" << status;
4039 if (
static_cast<QDateTime const
>(SchedulerModuleState::getLocalTime()) < activeJob()->getStartupTime())
4042 switch (activeJob()->getStage())
4044 case SCHEDSTAGE_SLEWING:
4046 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Slewing stage...";
4048 if (status == ISD::Mount::MOUNT_TRACKING)
4051 moduleState()->updateJobStage(SCHEDSTAGE_SLEW_COMPLETE);
4054 else if (status == ISD::Mount::MOUNT_ERROR)
4056 appendLogText(
i18n(
"Warning: job '%1' slew failed, marking terminated due to errors.", activeJob()->getName()));
4060 else if (status == ISD::Mount::MOUNT_IDLE)
4062 appendLogText(
i18n(
"Warning: job '%1' found not slewing, restarting.", activeJob()->getName()));
4063 moduleState()->updateJobStage(SCHEDSTAGE_IDLE);
4069 case SCHEDSTAGE_RESLEWING:
4071 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Re-slewing stage...";
4073 if (status == ISD::Mount::MOUNT_TRACKING)
4075 appendLogText(
i18n(
"Job '%1' repositioning is complete.", activeJob()->getName()));
4076 moduleState()->updateJobStage(SCHEDSTAGE_RESLEWING_COMPLETE);
4079 else if (status == ISD::Mount::MOUNT_ERROR)
4081 appendLogText(
i18n(
"Warning: job '%1' repositioning failed, marking terminated due to errors.", activeJob()->getName()));
4085 else if (status == ISD::Mount::MOUNT_IDLE)
4087 appendLogText(
i18n(
"Warning: job '%1' found not repositioning, restarting.", activeJob()->getName()));
4088 moduleState()->updateJobStage(SCHEDSTAGE_IDLE);
4096 case SCHEDSTAGE_FOCUSING:
4097 case SCHEDSTAGE_ALIGNING:
4098 case SCHEDSTAGE_GUIDING:
4099 if (status == ISD::Mount::MOUNT_PARKED)
4101 appendLogText(
i18n(
"Warning: Mount is parked while scheduler for job '%1' is active. Aborting.", activeJob()->getName()));
4108 case SCHEDSTAGE_CAPTURING:
4109 if (status == ISD::Mount::MOUNT_PARKED && activeJob() && activeJob()->getLightFramesRequired()
4110 && activeJob()->getCalibrationMountPark() ==
false)
4112 appendLogText(
i18n(
"Warning: Mount is parked while scheduler for job '%1' is active. Aborting.", activeJob()->getName()));
4122void SchedulerProcess::setWeatherStatus(ISD::Weather::Status status)
4124 ISD::Weather::Status newStatus = status;
4126 if (newStatus == moduleState()->weatherStatus())
4129 ISD::Weather::Status oldStatus = moduleState()->weatherStatus();
4130 moduleState()->setWeatherStatus(newStatus);
4133 if (moduleState()->preemptiveShutdown() &&
4134 oldStatus != ISD::Weather::WEATHER_OK &&
4135 newStatus == ISD::Weather::WEATHER_OK)
4138 moduleState()->setWeatherGracePeriodActive(
false);
4142 else if (activeJob() && Options::schedulerWeather() && (newStatus == ISD::Weather::WEATHER_ALERT &&
4143 moduleState()->schedulerState() != Ekos::SCHEDULER_IDLE &&
4144 moduleState()->schedulerState() != Ekos::SCHEDULER_SHUTDOWN))
4146 appendLogText(
i18n(
"Weather alert detected. Starting soft shutdown procedure."));
4157 QDateTime wakeupTime = SchedulerModuleState::getLocalTime().addSecs(Options::schedulerWeatherGracePeriod() * 60);
4158 moduleState()->setWeatherGracePeriodActive(
true);
4159 moduleState()->enablePreemptiveShutdown(wakeupTime);
4161 appendLogText(
i18n(
"Observatory scheduled for soft shutdown until weather improves or until %1.",
4165 emit schedulerSleeping(
true,
true);
4170 emit newWeatherStatus(status);
4173void SchedulerProcess::checkStartupProcedure()
4179void SchedulerProcess::checkShutdownProcedure()
4184 if (moduleState()->shutdownState() == SHUTDOWN_COMPLETE)
4188 if (Options::stopEkosAfterShutdown())
4191 else if (moduleState()->shutdownState() == SHUTDOWN_ERROR)
4194 moduleState()->setShutdownState(SHUTDOWN_IDLE);
4203void SchedulerProcess::parkCap()
4205 if (capInterface().isNull())
4208 moduleState()->setShutdownState(SHUTDOWN_ERROR);
4212 QVariant parkingStatus = capInterface()->property(
"parkStatus");
4213 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Cap parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
4215 if (parkingStatus.
isValid() ==
false)
4217 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: cap parkStatus request received DBUS error: %1").arg(
4218 mountInterface()->lastError().
type());
4220 parkingStatus = ISD::PARK_ERROR;
4223 ISD::ParkStatus status =
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt());
4225 if (status != ISD::PARK_PARKED)
4227 moduleState()->setShutdownState(SHUTDOWN_PARKING_CAP);
4228 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Parking dust cap...";
4232 moduleState()->startCurrentOperationTimer();
4237 moduleState()->setShutdownState(SHUTDOWN_PARK_MOUNT);
4241void SchedulerProcess::unParkCap()
4243 if (capInterface().isNull())
4245 appendLogText(
i18n(
"Dust cover unpark requested but no dust covers detected."));
4246 moduleState()->setStartupState(STARTUP_ERROR);
4250 QVariant parkingStatus = capInterface()->property(
"parkStatus");
4251 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Cap parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
4253 if (parkingStatus.
isValid() ==
false)
4255 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: cap parkStatus request received DBUS error: %1").arg(
4256 mountInterface()->lastError().
type());
4258 parkingStatus = ISD::PARK_ERROR;
4261 ISD::ParkStatus status =
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt());
4263 if (status != ISD::PARK_UNPARKED)
4265 moduleState()->setStartupState(STARTUP_UNPARKING_CAP);
4269 moduleState()->startCurrentOperationTimer();
4274 moduleState()->setStartupState(STARTUP_COMPLETE);
4278void SchedulerProcess::parkMount()
4280 if (mountInterface().isNull())
4283 moduleState()->setShutdownState(SHUTDOWN_ERROR);
4287 QVariant parkingStatus = mountInterface()->property(
"parkStatus");
4288 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Mount parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
4290 if (parkingStatus.
isValid() ==
false)
4292 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: mount parkStatus request received DBUS error: %1").arg(
4293 mountInterface()->lastError().
type());
4295 moduleState()->setParkWaitState(PARKWAIT_ERROR);
4298 ISD::ParkStatus status =
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt());
4302 case ISD::PARK_PARKED:
4303 if (moduleState()->shutdownState() == SHUTDOWN_PARK_MOUNT)
4304 moduleState()->setShutdownState(SHUTDOWN_PARK_DOME);
4306 moduleState()->setParkWaitState(PARKWAIT_PARKED);
4310 case ISD::PARK_UNPARKING:
4316 case ISD::PARK_ERROR:
4317 case ISD::PARK_UNKNOWN:
4318 case ISD::PARK_UNPARKED:
4320 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Parking mount...";
4321 QDBusReply<bool>
const mountReply = mountInterface()->call(
QDBus::AutoDetect,
"park");
4325 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: mount park request received DBUS error: %1").arg(
4328 moduleState()->setParkWaitState(PARKWAIT_ERROR);
4330 else moduleState()->startCurrentOperationTimer();
4334 case ISD::PARK_PARKING:
4336 if (moduleState()->shutdownState() == SHUTDOWN_PARK_MOUNT)
4337 moduleState()->setShutdownState(SHUTDOWN_PARKING_MOUNT);
4339 moduleState()->setParkWaitState(PARKWAIT_PARKING);
4350void SchedulerProcess::unParkMount()
4352 if (mountInterface().isNull())
4355 moduleState()->setStartupState(STARTUP_ERROR);
4359 QVariant parkingStatus = mountInterface()->property(
"parkStatus");
4360 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Mount parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
4362 if (parkingStatus.
isValid() ==
false)
4364 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: mount parkStatus request received DBUS error: %1").arg(
4365 mountInterface()->lastError().
type());
4367 moduleState()->setParkWaitState(PARKWAIT_ERROR);
4370 ISD::ParkStatus status =
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt());
4375 case ISD::PARK_UNPARKED:
4376 if (moduleState()->startupState() == STARTUP_UNPARK_MOUNT)
4377 moduleState()->setStartupState(STARTUP_UNPARK_CAP);
4379 moduleState()->setParkWaitState(PARKWAIT_UNPARKED);
4384 case ISD::PARK_PARKING:
4390 case ISD::PARK_ERROR:
4391 case ISD::PARK_UNKNOWN:
4392 case ISD::PARK_PARKED:
4394 QDBusReply<bool>
const mountReply = mountInterface()->call(
QDBus::AutoDetect,
"unpark");
4398 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: mount unpark request received DBUS error: %1").arg(
4401 moduleState()->setParkWaitState(PARKWAIT_ERROR);
4403 else moduleState()->startCurrentOperationTimer();
4408 case ISD::PARK_UNPARKING:
4409 if (moduleState()->startupState() == STARTUP_UNPARK_MOUNT)
4410 moduleState()->setStartupState(STARTUP_UNPARKING_MOUNT);
4412 moduleState()->setParkWaitState(PARKWAIT_UNPARKING);
4413 qCInfo(KSTARS_EKOS_SCHEDULER) <<
"Unparking mount in progress...";
4424 QVariant var = mountInterface()->property(
"equatorialCoords");
4427 if (var.isValid() ==
false || var.canConvert<
QList<double >> () ==
false)
4429 qCCritical(KSTARS_EKOS_SCHEDULER) <<
"Warning: reading equatorial coordinates received an unexpected value:" << var;
4434 if (coords.
size() != 2)
4436 qCCritical(KSTARS_EKOS_SCHEDULER) <<
"Warning: reading equatorial coordinates received" << coords.
size() <<
4437 "instead of 2 values: " << coords;
4441 return SkyPoint(coords[0], coords[1]);
4446 if (mountInterface().isNull())
4450 QVariant canPark = mountInterface()->property(
"canPark");
4451 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Mount can park:" << (!canPark.
isValid() ?
"invalid" : (canPark.
toBool() ?
"T" :
"F"));
4453 if (canPark.
isValid() ==
false)
4455 qCCritical(KSTARS_EKOS_SCHEDULER) <<
QString(
"Warning: mount canPark request received DBUS error: %1").
arg(
4456 mountInterface()->lastError().type());
4460 else if (canPark.
toBool() ==
true)
4464 QVariant parkingStatus = mountInterface()->property(
"parkStatus");
4465 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Mount parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
4467 if (parkingStatus.
isValid() ==
false)
4469 qCCritical(KSTARS_EKOS_SCHEDULER) <<
QString(
"Warning: mount parking status property is invalid %1.").
arg(
4470 mountInterface()->lastError().type());
4476 switch (
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt()))
4480 case ISD::PARK_PARKED:
4495void SchedulerProcess::parkDome()
4498 if (domeInterface().isNull())
4501 moduleState()->setShutdownState(SHUTDOWN_ERROR);
4507 QVariant parkingStatus = domeInterface()->property(
"parkStatus");
4508 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Dome parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
4510 if (parkingStatus.
isValid() ==
false)
4512 qCCritical(KSTARS_EKOS_SCHEDULER) <<
QString(
"Warning: dome parkStatus request received DBUS error: %1").
arg(
4513 mountInterface()->lastError().type());
4515 parkingStatus = ISD::PARK_ERROR;
4518 ISD::ParkStatus status =
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt());
4519 if (status != ISD::PARK_PARKED)
4521 moduleState()->setShutdownState(SHUTDOWN_PARKING_DOME);
4525 moduleState()->startCurrentOperationTimer();
4530 moduleState()->setShutdownState(SHUTDOWN_SCRIPT);
4534void SchedulerProcess::unParkDome()
4537 if (domeInterface().isNull())
4540 moduleState()->setStartupState(STARTUP_ERROR);
4544 QVariant parkingStatus = domeInterface()->property(
"parkStatus");
4545 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Dome parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
4547 if (parkingStatus.
isValid() ==
false)
4549 qCCritical(KSTARS_EKOS_SCHEDULER) << QString(
"Warning: dome parkStatus request received DBUS error: %1").arg(
4550 mountInterface()->lastError().
type());
4552 parkingStatus = ISD::PARK_ERROR;
4555 if (
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt()) != ISD::PARK_UNPARKED)
4557 moduleState()->setStartupState(STARTUP_UNPARKING_DOME);
4561 moduleState()->startCurrentOperationTimer();
4566 moduleState()->setStartupState(STARTUP_UNPARK_MOUNT);
4572 QVariant guideStatus = guideInterface()->property(
"status");
4573 Ekos::GuideState gStatus =
static_cast<Ekos::GuideState
>(guideStatus.
toInt());
4578const QString &SchedulerProcess::profile()
const
4580 return moduleState()->currentProfile();
4583void SchedulerProcess::setProfile(
const QString &newProfile)
4585 moduleState()->setCurrentProfile(newProfile);
4588QString SchedulerProcess::currentJobName()
4590 auto job = moduleState()->activeJob();
4591 return ( job !=
nullptr ? job->getName() : QString() );
4594QString SchedulerProcess::currentJobJson()
4596 auto job = moduleState()->activeJob();
4597 if( job !=
nullptr )
4599 return QString( QJsonDocument( job->toJson() ).toJson() );
4607QString SchedulerProcess::jsonJobs()
4609 return QString( QJsonDocument( moduleState()->getJSONJobs() ).toJson() );
4612QStringList SchedulerProcess::logText()
4614 return moduleState()->logText();
4619 if (domeInterface().isNull())
4622 QVariant parkingStatus = domeInterface()->property(
"parkStatus");
4623 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Dome parking status" << (!parkingStatus.
isValid() ? -1 : parkingStatus.
toInt());
4625 if (parkingStatus.
isValid() ==
false)
4627 qCCritical(KSTARS_EKOS_SCHEDULER) <<
QString(
"Warning: dome parkStatus request received DBUS error: %1").
arg(
4628 mountInterface()->lastError().type());
4630 parkingStatus = ISD::PARK_ERROR;
4633 ISD::ParkStatus status =
static_cast<ISD::ParkStatus
>(parkingStatus.
toInt());
4635 return status == ISD::PARK_PARKED;
4638void SchedulerProcess::simClockScaleChanged(
float newScale)
4640 if (moduleState()->currentlySleeping())
4643 (moduleState()->iterationTimer().remainingTime())
4644 * KStarsData::Instance()->clock()->scale()
4646 appendLogText(
i18n(
"Sleeping for %1 on simulation clock update until next observation job is ready...",
4647 remainingTimeMs.
toString(
"hh:mm:ss")));
4648 moduleState()->iterationTimer().stop();
4651 moduleState()->tickleTimer().stop();
4654void SchedulerProcess::simClockTimeChanged()
4656 moduleState()->calculateDawnDusk();
4659 if (SCHEDULER_RUNNING != moduleState()->schedulerState())
4663void SchedulerProcess::setINDICommunicationStatus(CommunicationStatus status)
4665 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Scheduler INDI status is" << status;
4667 moduleState()->setIndiCommunicationStatus(status);
4670void SchedulerProcess::setEkosCommunicationStatus(CommunicationStatus status)
4672 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Scheduler Ekos status is" << status;
4674 moduleState()->setEkosCommunicationStatus(status);
4679void SchedulerProcess::checkInterfaceReady(QDBusInterface * iface)
4681 if (iface == mountInterface())
4684 moduleState()->setMountReady(
true);
4686 else if (iface == capInterface())
4689 moduleState()->setCapReady(
true);
4691 else if (iface == observatoryInterface())
4693 QVariant status = observatoryInterface()->property(
"status");
4694 if (status.isValid())
4695 setWeatherStatus(
static_cast<ISD::Weather::Status
>(status.toInt()));
4697 else if (iface == weatherInterface())
4699 QVariant status = weatherInterface()->property(
"status");
4700 if (status.isValid())
4701 setWeatherStatus(
static_cast<ISD::Weather::Status
>(status.toInt()));
4703 else if (iface == domeInterface())
4706 moduleState()->setDomeReady(
true);
4708 else if (iface == captureInterface())
4711 moduleState()->setCaptureReady(
true);
4714 emit interfaceReady(iface);
4717void SchedulerProcess::registerNewModule(
const QString &name)
4719 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Registering new Module (" <<
name <<
")";
4721 if (name ==
"Focus")
4723 delete focusInterface();
4724 setFocusInterface(
new QDBusInterface(kstarsInterfaceString, focusPathString, focusInterfaceString,
4726 connect(focusInterface(), SIGNAL(newStatus(Ekos::FocusState,
const QString)),
this,
4729 else if (name ==
"Capture")
4731 delete captureInterface();
4732 setCaptureInterface(
new QDBusInterface(kstarsInterfaceString, capturePathString, captureInterfaceString,
4735 connect(captureInterface(), SIGNAL(ready()),
this, SLOT(syncProperties()));
4738 connect(captureInterface(), SIGNAL(captureComplete(QVariantMap,
const QString)),
this, SLOT(checkAlignment(QVariantMap,
4741 checkInterfaceReady(captureInterface());
4743 else if (name ==
"Mount")
4745 delete mountInterface();
4746 setMountInterface(
new QDBusInterface(kstarsInterfaceString, mountPathString, mountInterfaceString,
4749 connect(mountInterface(), SIGNAL(ready()),
this, SLOT(syncProperties()));
4750 connect(mountInterface(), SIGNAL(newStatus(ISD::Mount::Status)),
this, SLOT(setMountStatus(ISD::Mount::Status)),
4753 checkInterfaceReady(mountInterface());
4755 else if (name ==
"Align")
4757 delete alignInterface();
4758 setAlignInterface(
new QDBusInterface(kstarsInterfaceString, alignPathString, alignInterfaceString,
4763 else if (name ==
"Guide")
4765 delete guideInterface();
4766 setGuideInterface(
new QDBusInterface(kstarsInterfaceString, guidePathString, guideInterfaceString,
4768 connect(guideInterface(), SIGNAL(newStatus(Ekos::GuideState)),
this,
4771 else if (name ==
"Observatory")
4773 delete observatoryInterface();
4774 setObservatoryInterface(
new QDBusInterface(kstarsInterfaceString, observatoryPathString, observatoryInterfaceString,
4776 connect(observatoryInterface(), SIGNAL(newStatus(ISD::Weather::Status)),
this,
4778 checkInterfaceReady(observatoryInterface());
4782void SchedulerProcess::registerNewDevice(
const QString &name,
int interface)
4786 if (interface & INDI::BaseDevice::DOME_INTERFACE)
4788 QList<QVariant> dbusargs;
4789 dbusargs.
append(INDI::BaseDevice::DOME_INTERFACE);
4790 QDBusReply<QStringList> paths = indiInterface()->callWithArgumentList(
QDBus::AutoDetect,
"getDevicesPaths",
4795 setDomePathString(paths.value().last());
4796 delete domeInterface();
4797 setDomeInterface(
new QDBusInterface(kstarsInterfaceString, domePathString,
4798 domeInterfaceString,
4800 connect(domeInterface(), SIGNAL(ready()),
this, SLOT(syncProperties()));
4801 checkInterfaceReady(domeInterface());
4826 if (interface & INDI::BaseDevice::DUSTCAP_INTERFACE)
4828 QList<QVariant> dbusargs;
4829 dbusargs.
append(INDI::BaseDevice::DUSTCAP_INTERFACE);
4830 QDBusReply<QStringList> paths = indiInterface()->callWithArgumentList(
QDBus::AutoDetect,
"getDevicesPaths",
4835 setDustCapPathString(paths.value().last());
4836 delete capInterface();
4837 setCapInterface(
new QDBusInterface(kstarsInterfaceString, dustCapPathString,
4838 dustCapInterfaceString,
4840 connect(capInterface(), SIGNAL(ready()),
this, SLOT(syncProperties()));
4841 checkInterfaceReady(capInterface());
4848 XMLEle *ep =
nullptr;
4849 XMLEle *subEP =
nullptr;
4851 for (ep = nextXMLEle(root, 1); ep !=
nullptr; ep = nextXMLEle(root, 0))
4853 if (!strcmp(tagXMLEle(ep),
"Job"))
4855 for (subEP = nextXMLEle(ep, 1); subEP !=
nullptr; subEP = nextXMLEle(ep, 0))
4857 if (!strcmp(tagXMLEle(subEP),
"TargetName"))
4862 else if (!strcmp(tagXMLEle(subEP),
"FITSDirectory"))
4875 if (outputFile ==
nullptr)
4877 QString message =
i18n(
"Unable to write to file %1", filename);
4878 KSNotification::sorry(message,
i18n(
"Could Not Open File"));
4882 fprintf(outputFile,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
4883 prXMLEle(outputFile, root, 0);
4897 KSNotification::sorry(
i18n(
"Unable to open file %1", sFile.
fileName()),
4898 i18n(
"Could Not Open File"));
4902 LilXML *xmlParser = newLilXML();
4903 char errmsg[MAXRBUF];
4904 XMLEle *root =
nullptr;
4909 root = readXMLEle(xmlParser, c, errmsg);
4915 delLilXML(xmlParser);
4920void SchedulerProcess::checkProcessExit(
int exitCode)
4926 if (moduleState()->startupState() == STARTUP_SCRIPT)
4927 moduleState()->setStartupState(STARTUP_UNPARK_DOME);
4928 else if (moduleState()->shutdownState() == SHUTDOWN_SCRIPT_RUNNING)
4929 moduleState()->setShutdownState(SHUTDOWN_COMPLETE);
4934 if (moduleState()->startupState() == STARTUP_SCRIPT)
4937 moduleState()->setStartupState(STARTUP_ERROR);
4939 else if (moduleState()->shutdownState() == SHUTDOWN_SCRIPT_RUNNING)
4942 moduleState()->setShutdownState(SHUTDOWN_ERROR);
4947void SchedulerProcess::readProcessOutput()
4949 appendLogText(scriptProcess().readAllStandardOutput().simplified());
4952bool SchedulerProcess::canCountCaptures(
const SchedulerJob &job)
4954 QList<QSharedPointer<SequenceJob >> seqjobs;
4955 bool hasAutoFocus =
false;
4956 SchedulerJob tempJob = job;
4957 if (SchedulerUtils::loadSequenceQueue(tempJob.getSequenceFile().toLocalFile(), &tempJob, seqjobs, hasAutoFocus,
4961 for (
auto oneSeqJob : seqjobs)
4963 if (oneSeqJob->getUploadMode() == ISD::Camera::UPLOAD_REMOTE)
4977 forced |= std::any_of(moduleState()->jobs().begin(),
4978 moduleState()->jobs().end(), [](SchedulerJob * oneJob) ->
bool
4985 moduleState()->capturedFramesCount().clear();
4988 for (SchedulerJob *oneJob : moduleState()->jobs())
4995 bool hasAutoFocus =
false;
4999 if (SchedulerUtils::loadSequenceQueue(oneJob->getSequenceFile().
toLocalFile(), oneJob, seqjobs, hasAutoFocus,
5002 appendLogText(
i18n(
"Warning: job '%1' has inaccessible sequence '%2', marking invalid.", oneJob->getName(),
5008 oneJob->clearProgress();
5010 for (
auto oneSeqJob : seqjobs)
5014 if (oneSeqJob->getUploadMode() == ISD::Camera::UPLOAD_REMOTE)
5018 QString const signature = oneSeqJob->getSignature();
5026 const int count = newFramesCount.
constFind(signature).value();
5027 newJobFramesCount[signature] = count;
5028 oneJob->addProgress(count, oneSeqJob);
5034 CapturedFramesMap::const_iterator
const earlierRunIterator =
5035 moduleState()->capturedFramesCount().constFind(signature);
5037 if (moduleState()->capturedFramesCount().constEnd() != earlierRunIterator)
5039 count = earlierRunIterator.value();
5042 count = PlaceholderPath::getCompletedFiles(signature);
5044 newFramesCount[signature] = count;
5045 newJobFramesCount[signature] = count;
5046 oneJob->addProgress(count, oneSeqJob);
5050 SchedulerUtils::updateLightFramesRequired(oneJob, seqjobs, newFramesCount);
5053 moduleState()->setCapturedFramesCount(newFramesCount);
5056 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Frame map summary:";
5057 CapturedFramesMap::const_iterator it = moduleState()->capturedFramesCount().constBegin();
5058 for (; it != moduleState()->capturedFramesCount().constEnd(); it++)
5059 qCDebug(KSTARS_EKOS_SCHEDULER) <<
" " << it.key() <<
':' << it.value();
5063SchedulerJob *SchedulerProcess::activeJob()
5065 return moduleState()->activeJob();
5068void SchedulerProcess::printStates(
const QString &label)
5070 qCDebug(KSTARS_EKOS_SCHEDULER) <<
5071 QString(
"%1 %2 %3%4 %5 %6 %7 %8 %9\n")
5073 .
arg(timerStr(moduleState()->timerState()))
5074 .
arg(getSchedulerStatusString(moduleState()->schedulerState()))
5075 .
arg((moduleState()->timerState() == RUN_JOBCHECK && activeJob() !=
nullptr) ?
5076 QString(
"(%1 %2)").arg(SchedulerJob::jobStatusString(activeJob()->getState()))
5077 .arg(SchedulerJob::jobStageString(activeJob()->getStage())) :
"")
5078 .
arg(ekosStateString(moduleState()->ekosState()))
5079 .
arg(indiStateString(moduleState()->indiState()))
5080 .
arg(startupStateString(moduleState()->startupState()))
5081 .
arg(shutdownStateString(moduleState()->shutdownState()))
5082 .
arg(parkWaitStateString(moduleState()->parkWaitState())).
toLatin1().
data();
5083 foreach (
auto j, moduleState()->jobs())
5084 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"job %1 %2\n").
arg(j->getName()).
arg(SchedulerJob::jobStatusString(
Q_SCRIPTABLE Q_NOREPLY void startAstrometry()
startAstrometry initiation of the capture and solve operation.
bool shouldSchedulerSleep(SchedulerJob *job)
shouldSchedulerSleep Check if the scheduler needs to sleep until the job is ready
Q_SCRIPTABLE Q_NOREPLY void startCapture(bool restart=false)
startCapture The current job file name is solved to an url which is fed to ekos.
void loadProfiles()
loadProfiles Load the existing EKOS profiles
Q_SCRIPTABLE Q_NOREPLY void runStartupProcedure()
runStartupProcedure Execute the startup of the scheduler itself to be prepared for running scheduler ...
void checkCapParkingStatus()
checkDomeParkingStatus check dome parking status and updating corresponding states accordingly.
void getNextAction()
getNextAction Checking for the next appropriate action regarding the current state of the scheduler a...
Q_SCRIPTABLE bool isMountParked()
Q_SCRIPTABLE Q_NOREPLY void startJobEvaluation()
startJobEvaluation Start job evaluation only without starting the scheduler process itself.
Q_SCRIPTABLE Q_NOREPLY void resetJobs()
resetJobs Reset all jobs counters
void selectActiveJob(const QList< SchedulerJob * > &jobs)
selectActiveJob Select the job that should be executed
Q_SCRIPTABLE void wakeUpScheduler()
wakeUpScheduler Wake up scheduler from sleep state
Q_SCRIPTABLE Q_NOREPLY void setPaused()
setPaused pausing the scheduler
Q_SCRIPTABLE bool checkParkWaitState()
checkParkWaitState Check park wait state.
bool executeJob(SchedulerJob *job)
executeJob After the best job is selected, we call this in order to start the process that will execu...
bool createJobSequence(XMLEle *root, const QString &prefix, const QString &outputDir)
createJobSequence Creates a job sequence for the mosaic tool given the prefix and output dir.
void iterate()
Repeatedly runs a scheduler iteration and then sleeps timerInterval millisconds and run the next iter...
Q_SCRIPTABLE Q_NOREPLY void startGuiding(bool resetCalibration=false)
startGuiding After ekos is fed the calibration options, we start the guiding process
void findNextJob()
findNextJob Check if the job met the completion criteria, and if it did, then it search for next job ...
Q_SCRIPTABLE bool appendEkosScheduleList(const QString &fileURL)
appendEkosScheduleList Append the contents of an ESL file to the queue.
Q_SCRIPTABLE void execute()
execute Execute the schedule, start if idle or paused.
Q_SCRIPTABLE bool isDomeParked()
Q_SCRIPTABLE bool saveScheduler(const QUrl &fileURL)
saveScheduler Save scheduler jobs to a file
Q_SCRIPTABLE Q_NOREPLY void appendLogText(const QString &logentry) override
appendLogText Append a new line to the logging.
Q_SCRIPTABLE Q_NOREPLY void start()
DBUS interface function.
Q_SCRIPTABLE Q_NOREPLY void removeAllJobs()
DBUS interface function.
Q_SCRIPTABLE void stopCurrentJobAction()
stopCurrentJobAction Stop whatever action taking place in the current job (eg.
Q_SCRIPTABLE Q_NOREPLY void stopGuiding()
stopGuiding After guiding is done we need to stop the process
bool checkShutdownState()
checkShutdownState Check shutdown procedure stages and make sure all stages are complete.
Q_SCRIPTABLE Q_NOREPLY void startSlew()
startSlew DBus call for initiating slew
bool checkEkosState()
checkEkosState Check ekos startup stages and take whatever action necessary to get Ekos up and runnin...
bool checkStatus()
checkJobStatus Check the overall state of the scheduler, Ekos, and INDI.
bool checkINDIState()
checkINDIState Check INDI startup stages and take whatever action necessary to get INDI devices conne...
XMLEle * getSequenceJobRoot(const QString &filename) const
getSequenceJobRoot Read XML data from capture sequence job
Q_SCRIPTABLE Q_NOREPLY void runShutdownProcedure()
runShutdownProcedure Shutdown the scheduler itself and EKOS (if configured to do so).
Q_SCRIPTABLE Q_NOREPLY void setSequence(const QString &sequenceFileURL)
DBUS interface function.
Q_SCRIPTABLE bool loadScheduler(const QString &fileURL)
DBUS interface function.
Q_SCRIPTABLE Q_NOREPLY void startFocusing()
startFocusing DBus call for feeding ekos the specified settings and initiating focus operation
void checkJobStage()
checkJobStage Check the progress of the job states and make DBUS calls to start the next stage until ...
bool checkStartupState()
checkStartupState Check startup procedure stages and make sure all stages are complete.
void processGuidingTimer()
processGuidingTimer Check the guiding timer, and possibly restart guiding.
SkyPoint mountCoords()
mountCoords read the equatorial coordinates from the mount
GuideState getGuidingStatus()
getGuidingStatus Retrieve the guiding status.
Q_SCRIPTABLE Q_NOREPLY void resetAllJobs()
DBUS interface function.
void applyConfig()
applyConfig Apply configuration changes from the global configuration dialog.
Q_SCRIPTABLE void clearLog()
clearLog Clear log entry
Q_SCRIPTABLE Q_NOREPLY void disconnectINDI()
disconnectINDI disconnect all INDI devices from server.
Q_SCRIPTABLE Q_NOREPLY void stop()
DBUS interface function.
bool manageConnectionLoss()
manageConnectionLoss Mitigate loss of connection with the INDI server.
void updateCompletedJobsCount(bool forced=false)
updateCompletedJobsCount For each scheduler job, examine sequence job storage and count captures.
Q_SCRIPTABLE Q_NOREPLY void evaluateJobs(bool evaluateOnly)
evaluateJobs evaluates the current state of each objects and gives each one a score based on the cons...
int runSchedulerIteration()
Run a single scheduler iteration.
void setSolverAction(Align::GotoMode mode)
setSolverAction set the GOTO mode for the solver
Q_SCRIPTABLE bool completeShutdown()
completeShutdown Try to complete the scheduler shutdown
static KConfigDialog * exists(const QString &name)
void settingsChanged(const QString &dialogName)
static KStars * Instance()
The SchedulerState class holds all attributes defining the scheduler's state.
void timeChanged()
The time has changed (emitted by setUTC() )
void scaleChanged(float)
The timestep has changed.
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 & dec() const
const CachingDms & ra0() const
const CachingDms & ra() const
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 QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
const QString toHMSString(const bool machineReadable=false, const bool highPrecision=false) const
const double & Degrees() const
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
Ekos is an advanced Astrophotography tool for Linux.
SchedulerJobStatus
States of a SchedulerJob.
@ 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
ErrorHandlingStrategy
options what should happen if an error or abort occurs
@ ALIGN_FAILED
Alignment failed.
@ ALIGN_ABORTED
Alignment aborted by user or agent.
@ ALIGN_IDLE
No ongoing operations.
@ ALIGN_COMPLETE
Alignment successfully completed.
CaptureState
Capture states.
SchedulerTimerState
IterationTypes, the different types of scheduler iterations that are run.
GeoCoordinates geo(const QVariant &location)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
bool isValid(QStringView ifopt)
QString name(StandardAction id)
const char * constData() const const
QDateTime addSecs(qint64 s) const const
qint64 currentMSecsSinceEpoch()
qint64 secsTo(const QDateTime &other) const const
QString toString(QStringView format, QCalendar cal) const const
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
QDBusConnection sessionBus()
void unregisterObject(const QString &path, UnregisterMode mode)
QString errorString(ErrorType error)
QString message() const const
ErrorType type() const const
QList< QVariant > arguments() const const
QString errorMessage() const const
const QDBusError & error()
bool isValid() const const
bool mkpath(const QString &dirPath) const const
bool exists(const QString &fileName)
virtual QString fileName() const const override
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
void setFileName(const QString &name)
virtual void close() override
void append(QList< T > &&value)
bool isEmpty() const const
qsizetype size() const const
T value(qsizetype i) const const
int toInt(QStringView s, bool *ok) const const
QString toString(QDate date, FormatType format) const const
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
QList< Key > keys() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QVariant property(const char *name) const const
void finished(int exitCode, QProcess::ExitStatus exitStatus)
void readyReadStandardOutput()
void start(OpenMode mode)
QString arg(Args &&... args) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QByteArray toLatin1() const const
std::string toStdString() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QTextStream & dec(QTextStream &stream)
QTextStream & endl(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QTime fromMSecsSinceStartOfDay(int msecs)
int msecsSinceStartOfDay() const const
QString toString(QStringView format) const const
bool isEmpty() const const
bool isValid() const const
QString toLocalFile() const const
bool isValid() const const
bool toBool() const const
int toInt(bool *ok) const const
QString toString() const const