12#include "ekos/scheduler/framingassistantui.h"
13#include "ksnotification.h"
14#include "ksmessagebox.h"
16#include "kstarsdata.h"
19#include "scheduleradaptor.h"
20#include "schedulerjob.h"
21#include "schedulerprocess.h"
22#include "schedulermodulestate.h"
23#include "schedulerutils.h"
24#include "scheduleraltitudegraph.h"
25#include "skymapcomposite.h"
26#include "skycomponents/mosaiccomponent.h"
27#include "skyobjects/mosaictiles.h"
28#include "auxiliary/QProgressIndicator.h"
29#include "dialogs/finddialog.h"
30#include "ekos/manager.h"
31#include "ekos/capture/sequencejob.h"
32#include "ekos/capture/placeholderpath.h"
33#include "skyobjects/starobject.h"
34#include "greedyscheduler.h"
35#include "ekos/auxiliary/opticaltrainmanager.h"
36#include "ekos/auxiliary/solverutils.h"
37#include "ekos/auxiliary/stellarsolverprofile.h"
40#include <KConfigDialog>
41#include <KActionCollection>
46#include <ekos_scheduler_debug.h>
48#include "ekos/capture/sequenceeditor.h"
54#define INDEX_FOLLOWER 1
56#define BAD_SCORE -1000
57#define RESTART_GUIDING_DELAY_MS 5000
59#define DEFAULT_MIN_ALTITUDE 15
60#define DEFAULT_MIN_MOON_SEPARATION 0
65#define TEST_PRINT if (false) fprintf
89 setupScheduler(ekosPathString, ekosInterfaceString);
96 schedulerPathString = path;
97 kstarsInterfaceString = interface;
98 setupScheduler(ekosPathStr, ekosInterfaceStr);
101void Scheduler::setupScheduler(
const QString &ekosPathStr,
const QString &ekosInterfaceStr)
105 qRegisterMetaType<Ekos::SchedulerState>(
"Ekos::SchedulerState");
106 qDBusRegisterMetaType<Ekos::SchedulerState>();
108 m_moduleState.
reset(
new SchedulerModuleState());
109 m_process.reset(
new SchedulerProcess(moduleState(), ekosPathStr, ekosInterfaceStr));
114 QDateTime currentDateTime = SchedulerModuleState::getLocalTime();
115 QTime currentTime = currentDateTime.
time();
117 currentDateTime.
setTime(currentTime);
120 startupTimeEdit->setDateTime(currentDateTime);
121 schedulerUntilValue->setDateTime(currentDateTime);
132 leadFollowerSelectionCB->setModel(model);
134 sleepLabel->setPixmap(
136 changeSleepLabel(
"",
false);
139 bottomLayout->addWidget(pi, 0);
141 geo = KStarsData::Instance()->
geo();
144 raBox->setUnits(dmsBox::HOURS);
148 queueTable->setToolTip(
149 i18n(
"Job scheduler list.\nClick to select a job in the list.\nDouble click to edit a job with the left-hand fields.\nShift click to view a job's altitude tonight."));
150 QTableWidgetItem *statusHeader = queueTable->horizontalHeaderItem(SCHEDCOL_STATUS);
151 QTableWidgetItem *altitudeHeader = queueTable->horizontalHeaderItem(SCHEDCOL_ALTITUDE);
152 QTableWidgetItem *startupHeader = queueTable->horizontalHeaderItem(SCHEDCOL_STARTTIME);
153 QTableWidgetItem *completionHeader = queueTable->horizontalHeaderItem(SCHEDCOL_ENDTIME);
154 QTableWidgetItem *captureCountHeader = queueTable->horizontalHeaderItem(SCHEDCOL_CAPTURES);
156 if (statusHeader !=
nullptr)
157 statusHeader->
setToolTip(
i18n(
"Current status of the job, managed by the Scheduler.\n"
158 "If invalid, the Scheduler was not able to find a proper observation time for the target.\n"
159 "If aborted, the Scheduler missed the scheduled time or encountered transitory issues and will reschedule the job.\n"
160 "If complete, the Scheduler verified that all sequence captures requested were stored, including repeats."));
161 if (altitudeHeader !=
nullptr)
162 altitudeHeader->
setToolTip(
i18n(
"Current altitude of the target of the job.\n"
163 "A rising target is indicated with an arrow going up.\n"
164 "A setting target is indicated with an arrow going down."));
165 if (startupHeader !=
nullptr)
166 startupHeader->
setToolTip(
i18n(
"Startup time of the job, as estimated by the Scheduler.\n"
167 "The altitude at startup, if available, is displayed too.\n"
168 "Fixed time from user or culmination time is marked with a chronometer symbol."));
169 if (completionHeader !=
nullptr)
170 completionHeader->
setToolTip(
i18n(
"Completion time for the job, as estimated by the Scheduler.\n"
171 "You may specify a fixed time to limit duration of looping jobs. "
172 "A warning symbol indicates the altitude at completion may cause the job to abort before completion.\n"));
173 if (captureCountHeader !=
nullptr)
174 captureCountHeader->
setToolTip(
i18n(
"Count of captures stored for the job, based on its sequence job.\n"
175 "This is a summary, additional specific frame types may be required to complete the job."));
181 removeFromQueueB->setToolTip(
182 i18n(
"Remove selected job from the observation list.\nJob properties are copied in the edition fields before removal."));
186 queueUpB->setToolTip(
i18n(
"Move selected job one line up in the list.\n"));
189 queueDownB->setToolTip(
i18n(
"Move selected job one line down in the list.\n"));
193 evaluateOnlyB->setToolTip(
i18n(
"Reset state and force reevaluation of all observation jobs."));
196 sortJobsB->setToolTip(
197 i18n(
"Reset state and sort observation jobs per altitude and movement in sky, using the start time of the first job.\n"
198 "This action sorts setting targets before rising targets, and may help scheduling when starting your observation.\n"
199 "Note the algorithm first calculates all altitudes using the same time, then evaluates jobs."));
204 positionAngleSpin->setSpecialValueText(
"--");
219 selectShutdownScriptB->setIcon(
234 schedulerRepeatEverything->setEnabled(Options::rememberJobProgress() ==
false);
235 executionSequenceLimit->setEnabled(Options::rememberJobProgress() ==
false);
236 executionSequenceLimit->setValue(Options::schedulerExecutionSequencesLimit());
239 leadFollowerSelectionCB->setEnabled(
false);
249 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated,
this, &Scheduler::refreshOpticalTrain);
253 mosaicB->setDown(checked);
288 pauseB->setCheckable(
false);
307 connect(moduleState().data(), &SchedulerModuleState::ekosStateChanged,
this, &Scheduler::ekosStateChanged);
308 connect(moduleState().data(), &SchedulerModuleState::indiStateChanged,
this, &Scheduler::indiStateChanged);
309 connect(moduleState().data(), &SchedulerModuleState::indiCommunicationStatusChanged,
this,
310 &Scheduler::indiCommunicationStatusChanged);
312 connect(moduleState().data(), &SchedulerModuleState::startupStateChanged,
this, &Scheduler::startupStateChanged);
313 connect(moduleState().data(), &SchedulerModuleState::shutdownStateChanged,
this, &Scheduler::shutdownStateChanged);
314 connect(moduleState().data(), &SchedulerModuleState::parkWaitStateChanged,
this, &Scheduler::parkWaitStateChanged);
315 connect(moduleState().data(), &SchedulerModuleState::profilesChanged,
this, &Scheduler::updateProfiles);
317 connect(moduleState().data(), &SchedulerModuleState::jobStageChanged,
this, &Scheduler::updateJobStageUI);
319 connect(moduleState().data(), &SchedulerModuleState::currentProfileChanged,
this, [&]()
321 schedulerProfileCombo->setCurrentText(moduleState()->currentProfile());
326 connect(process().data(), &SchedulerProcess::shutdownStarted,
this, &Scheduler::handleShutdownStarted);
328 connect(process().data(), &SchedulerProcess::jobsUpdated,
this, &Scheduler::handleJobsUpdated);
329 connect(process().data(), &SchedulerProcess::targetDistance,
this, &Scheduler::targetDistance);
334 connect(process().data(), &SchedulerProcess::jobStarted,
this, &Scheduler::jobStarted);
335 connect(process().data(), &SchedulerProcess::jobEnded,
this, &Scheduler::jobEnded);
336 connect(process().data(), &SchedulerProcess::syncGreedyParams,
this, &Scheduler::syncGreedyParams);
338 connect(process().data(), &SchedulerProcess::changeSleepLabel,
this, &Scheduler::changeSleepLabel);
341 connect(process().data(), &SchedulerProcess::newWeatherStatus,
this, &Scheduler::setWeatherStatus);
354 Options::setErrorHandlingStrategy(strategy);
355 errorHandlingStrategyDelay->setEnabled(strategy != ERROR_DONT_RESTART);
359 Options::setErrorHandlingStrategyDelay(value);
363 if (Options::schedulerAlgorithm() != ALGORITHM_GREEDY)
365 process()->appendLogText(
366 i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
367 Options::setSchedulerAlgorithm(ALGORITHM_GREEDY);
371 setAlgorithm(Options::schedulerAlgorithm());
377 center.catalogueCoord(KStarsData::Instance()->updateNum()->julianDay());
378 raBox->show(
center.ra0());
379 decBox->show(
center.dec0());
386 if (!m_SequenceEditor)
387 m_SequenceEditor.
reset(
new SequenceEditor(
this));
389 m_SequenceEditor->show();
390 m_SequenceEditor->raise();
397 emit jobsUpdated(moduleState()->getJSONJobs());
400 moduleState()->calculateDawnDusk();
401 process()->loadProfiles();
405 loadGlobalSettings();
407 refreshOpticalTrain();
410QString Scheduler::getCurrentJobName()
412 return (activeJob() !=
nullptr ? activeJob()->getName() :
"");
418 if (enable == jobChangesAreWatched)
430 schedulerStartupScript,
431 schedulerShutdownScript
442 schedulerProfileCombo,
444 leadFollowerSelectionCB
450 errorHandlingButtonGroup,
452 constraintButtonGroup,
453 completionButtonGroup,
454 startupProcedureButtonGroup,
455 shutdownProcedureGroup
460 errorHandlingRescheduleErrorsCB
465 schedulerExecutionSequencesLimit,
466 errorHandlingStrategyDelay
471 schedulerMoonSeparationValue,
472 schedulerAltitudeValue,
487 for (
auto *
const control : lineEdits)
492 for (
auto *
const control : dateEdits)
497 for (
auto *
const control : comboBoxes)
499 if (control == leadFollowerSelectionCB)
501 this, [
this](
int pos)
503 setJobManipulation(queueUpB->isEnabled() || queueDownB->isEnabled(), removeFromQueueB->isEnabled(),
pos == INDEX_LEAD);
512 for (
auto *
const control : buttonGroups)
513#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
521 for (
auto *
const control : buttons)
526 for (
auto *
const control : spinBoxes)
531 for (
auto *
const control : dspinBoxes)
544 for (
auto *
const control : lineEdits)
546 for (
auto *
const control : dateEdits)
548 for (
auto *
const control : comboBoxes)
550 for (
auto *
const control : buttons)
552 for (
auto *
const control : buttonGroups)
553#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
558 for (
auto *
const control : spinBoxes)
560 for (
auto *
const control : dspinBoxes)
564 jobChangesAreWatched = enable;
569 schedulerRepeatEverything->
setEnabled(Options::rememberJobProgress() ==
false);
570 executionSequenceLimit->setEnabled(Options::rememberJobProgress() ==
false);
575 if (FindDialog::Instance()->execWithParent(Ekos::Manager::Instance()) ==
QDialog::Accepted)
582void Scheduler::addObject(
SkyObject *
object)
584 if (
object !=
nullptr)
588 if (object->
name() ==
"star")
596 nameEdit->setText(finalObjectName);
597 raBox->show(object->
ra0());
598 decBox->show(object->
dec0());
607 "FITS (*.fits *.fit);;XISF (*.xisf)");
611 processFITSSelection(url);
614void Scheduler::processFITSSelection(
const QUrl &url)
624 const QString filename = fitsEdit->text();
626 double ra = 0, dec = 0;
628 char comment[128], error_status[512];
629 fitsfile *fptr =
nullptr;
631 if (fits_open_diskfile(&fptr, filename.
toLatin1(), READONLY, &status))
633 fits_report_error(stderr, status);
634 fits_get_errstatus(status, error_status);
640 if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status))
642 fits_report_error(stderr, status);
643 fits_get_errstatus(status, error_status);
649 char objectra_str[32] = {0};
650 if (fits_read_key(fptr, TSTRING,
"OBJCTRA", objectra_str, comment, &status))
652 if (fits_read_key(fptr, TDOUBLE,
"RA", &ra, comment, &status))
654 fits_report_error(stderr, status);
655 fits_get_errstatus(status, error_status);
656 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTRA (%1).",
QString(error_status)));
668 char objectde_str[32] = {0};
669 if (fits_read_key(fptr, TSTRING,
"OBJCTDEC", objectde_str, comment, &status))
671 if (fits_read_key(fptr, TDOUBLE,
"DEC", &dec, comment, &status))
673 fits_report_error(stderr, status);
674 fits_get_errstatus(status, error_status);
675 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTDEC (%1).",
QString(error_status)));
689 char object_str[256] = {0};
690 if (fits_read_key(fptr, TSTRING,
"OBJECT", object_str, comment, &status))
693 nameEdit->setText(info.completeBaseName());
697 nameEdit->setText(object_str);
718 i18n(
"Ekos Sequence Queue (*.esq)"));
726 "Select Startup Script"),
728 i18n(
"Script (*)")));
729 if (moduleState()->startupScriptURL().isEmpty())
734 moduleState()->setDirty(
true);
735 schedulerStartupScript->setText(moduleState()->startupScriptURL().toLocalFile());
741 "Select Shutdown Script"),
743 i18n(
"Script (*)")));
744 if (moduleState()->shutdownScriptURL().isEmpty())
749 moduleState()->setDirty(
true);
750 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toLocalFile());
755 if (0 <= jobUnderEdit)
758 job = moduleState()->jobs().at(jobUnderEdit);
768 int currentRow = moduleState()->currentPosition();
772 currentRow = queueTable->rowCount();
780 if (moduleState()->jobs().count() > currentRow)
781 moduleState()->setCurrentPosition(currentRow);
784 emit jobsUpdated(moduleState()->getJSONJobs());
791 auto job = moduleState()->jobs().at(index);
798 emit jobsUpdated(moduleState()->getJSONJobs());
805 if (nameEdit->text().isEmpty())
807 process()->appendLogText(
i18n(
"Warning: Target name is required."));
811 if (sequenceEdit->text().isEmpty())
813 process()->appendLogText(
i18n(
"Warning: Sequence file is required."));
818 if ((raBox->isEmpty() || decBox->isEmpty()) && fitsURL.
isEmpty())
820 process()->appendLogText(
i18n(
"Warning: Target coordinates are required."));
824 bool raOk =
false, decOk =
false;
825 dms ra(raBox->createDms(&raOk));
826 dms dec(decBox->createDms(&decOk));
830 process()->appendLogText(
i18n(
"Warning: RA value %1 is invalid.", raBox->text()));
836 process()->appendLogText(
i18n(
"Warning: DEC value %1 is invalid.", decBox->text()));
846 if (asapConditionR->isChecked())
847 startCondition = START_ASAP;
850 if (schedulerCompleteSequences->isChecked())
851 stopCondition = FINISH_SEQUENCE;
852 else if (schedulerRepeatSequences->isChecked())
853 stopCondition = FINISH_REPEAT;
854 else if (schedulerUntilTerminated->isChecked())
855 stopCondition = FINISH_LOOP;
857 double altConstraint = SchedulerJob::UNDEFINED_ALTITUDE;
858 if (schedulerAltitude->isChecked())
859 altConstraint = schedulerAltitudeValue->value();
861 double moonConstraint = -1;
862 if (schedulerMoonSeparation->isChecked())
863 moonConstraint = schedulerMoonSeparationValue->value();
865 QString train = opticalTrainCombo->currentText() ==
"--" ?
"" : opticalTrainCombo->currentText();
869 SchedulerUtils::setupJob(*job, nameEdit->text(), leadFollowerSelectionCB->currentIndex() == INDEX_LEAD, groupEdit->text(),
871 KStarsData::Instance()->
ut().
djd(),
872 positionAngleSpin->value(), sequenceURL, fitsURL,
874 startCondition, startupTimeEdit->dateTime(),
875 stopCondition, schedulerUntilValue->dateTime(), schedulerExecutionSequencesLimit->value(),
879 schedulerWeather->isChecked(),
880 schedulerTwilight->isChecked(),
881 schedulerHorizon->isChecked(),
883 schedulerTrackStep->isChecked(),
884 schedulerFocusStep->isChecked(),
885 schedulerAlignStep->isChecked(),
886 schedulerGuideStep->isChecked());
898 int currentRow = moduleState()->currentPosition() + 1;
903 if (0 <= jobUnderEdit)
906 if (jobUnderEdit != currentRow - 1)
908 qCWarning(KSTARS_EKOS_SCHEDULER) <<
"BUG: the observation job under edit does not match the selected row in the job table.";
912 job = moduleState()->jobs().at(jobUnderEdit);
925 job =
new SchedulerJob();
935 moduleState()->mutlableJobs().insert(currentRow, job);
941 job->setLeadJob(moduleState()->findLead(currentRow - 1));
942 moduleState()->refreshFollowerLists();
950 foreach (SchedulerJob *a_job, moduleState()->jobs())
952 if (a_job == job || !a_job->isLead())
956 else if (a_job->getName() == job->getName())
958 int const a_job_row = moduleState()->jobs().indexOf(a_job);
961 process()->appendLogText(
i18n(
"Warning: job '%1' at row %2 has a duplicate target at row %3, "
962 "the scheduler may consider the same storage for captures.",
963 job->getName(), currentRow, a_job_row));
966 if (a_job->getSequenceFile() == job->getSequenceFile())
968 if (a_job->getRepeatsRequired() == job->getRepeatsRequired() && Options::rememberJobProgress())
969 process()->appendLogText(
i18n(
"Warning: jobs '%1' at row %2 and %3 probably require a different repeat count "
970 "as currently they will complete simultaneously after %4 batches (or disable option 'Remember job progress')",
971 job->getName(), currentRow, a_job_row, job->getRepeatsRequired()));
975 if (++numWarnings >= 1)
977 process()->appendLogText(
i18n(
"Skipped checking for duplicates."));
987 queueSaveAsB->setEnabled(
true);
988 queueSaveB->setEnabled(
true);
989 startB->setEnabled(
true);
990 evaluateOnlyB->setEnabled(
true);
992 checkJobInputComplete();
994 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 was saved.").
arg(job->getName()).
arg(currentRow + 1);
998 if (SCHEDULER_LOADING != moduleState()->schedulerState())
1000 process()->evaluateJobs(
true);
1006 nameEdit->setText(job->getName());
1007 groupEdit->setText(job->getGroup());
1009 raBox->show(job->getTargetCoords().
ra0());
1010 decBox->show(job->getTargetCoords().
dec0());
1013 fitsURL = job->getFITSFile().
isEmpty() ?
QUrl() : job->getFITSFile();
1016 schedulerTrackStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_TRACK);
1017 schedulerFocusStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_FOCUS);
1018 schedulerAlignStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_ALIGN);
1019 schedulerGuideStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_GUIDE);
1021 switch (job->getFileStartupCondition())
1024 asapConditionR->setChecked(
true);
1028 startupTimeConditionR->setChecked(
true);
1029 startupTimeEdit->setDateTime(job->getStartupTime());
1033 if (job->getMinAltitude())
1035 schedulerAltitude->setChecked(
true);
1036 schedulerAltitudeValue->setValue(job->getMinAltitude());
1040 schedulerAltitude->setChecked(
false);
1041 schedulerAltitudeValue->setValue(DEFAULT_MIN_ALTITUDE);
1044 if (job->getMinMoonSeparation() >= 0)
1046 schedulerMoonSeparation->setChecked(
true);
1047 schedulerMoonSeparationValue->setValue(job->getMinMoonSeparation());
1051 schedulerMoonSeparation->setChecked(
false);
1052 schedulerMoonSeparationValue->setValue(DEFAULT_MIN_MOON_SEPARATION);
1055 schedulerWeather->setChecked(job->getEnforceWeather());
1057 schedulerTwilight->blockSignals(
true);
1058 schedulerTwilight->setChecked(job->getEnforceTwilight());
1059 schedulerTwilight->blockSignals(
false);
1061 schedulerHorizon->blockSignals(
true);
1062 schedulerHorizon->setChecked(job->getEnforceArtificialHorizon());
1063 schedulerHorizon->blockSignals(
false);
1067 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1071 leadFollowerSelectionCB->setCurrentIndex(INDEX_FOLLOWER);
1074 if (job->getOpticalTrain().
isEmpty())
1075 opticalTrainCombo->setCurrentIndex(0);
1077 opticalTrainCombo->setCurrentText(job->getOpticalTrain());
1079 sequenceURL = job->getSequenceFile();
1082 positionAngleSpin->setValue(job->getPositionAngle());
1084 switch (job->getCompletionCondition())
1086 case FINISH_SEQUENCE:
1087 schedulerCompleteSequences->setChecked(
true);
1091 schedulerRepeatSequences->setChecked(
true);
1092 schedulerExecutionSequencesLimit->setValue(job->getRepeatsRequired());
1096 schedulerUntilTerminated->setChecked(
true);
1100 schedulerUntil->setChecked(
true);
1101 schedulerUntilValue->setDateTime(job->getFinishAtTime());
1111 schedulerParkDome->setChecked(Options::schedulerParkDome());
1112 schedulerParkMount->setChecked(Options::schedulerParkMount());
1113 schedulerCloseDustCover->setChecked(Options::schedulerCloseDustCover());
1114 schedulerWarmCCD->setChecked(Options::schedulerWarmCCD());
1115 schedulerUnparkDome->setChecked(Options::schedulerUnparkDome());
1116 schedulerUnparkMount->setChecked(Options::schedulerUnparkMount());
1117 schedulerOpenDustCover->setChecked(Options::schedulerOpenDustCover());
1119 errorHandlingStrategyDelay->setValue(Options::errorHandlingStrategyDelay());
1120 errorHandlingRescheduleErrorsCB->setChecked(Options::rescheduleErrors());
1122 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toString(
QUrl::PreferLocalFile));
1124 if (process()->captureInterface() !=
nullptr)
1126 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
1127 if (hasCoolerControl.
isValid())
1129 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
1130 moduleState()->setCaptureReady(
true);
1138 if (job ==
nullptr && moduleState()->jobs().
size() > 0)
1140 int const currentRow = moduleState()->currentPosition();
1141 if (0 <= currentRow && currentRow < moduleState()->jobs().
size())
1142 job = moduleState()->jobs().at(currentRow);
1146 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Cannot update night time, no matching job found at line" << currentRow;
1151 QDateTime const dawn = job ? job->getDawnAstronomicalTwilight() : moduleState()->Dawn();
1152 QDateTime const dusk = job ? job->getDuskAstronomicalTwilight() : moduleState()->Dusk();
1154 QChar const warning(dawn == dusk ? 0x26A0 :
'-');
1158bool Scheduler::modifyJob(
int index)
1166 queueTable->selectRow(index);
1167 auto modelIndex = queueTable->model()->index(index, 0);
1174 if (jobUnderEdit == i.
row())
1177 SchedulerJob *
const job = moduleState()->jobs().at(i.
row());
1192 startB->setEnabled(
false);
1193 evaluateOnlyB->setEnabled(
false);
1198 jobUnderEdit = i.
row();
1199 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is currently edited.").
arg(job->getName()).
arg(
1209 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.
fileName());
1214 Q_UNUSED(deselected)
1217 if (jobChangesAreWatched ==
false || selected.
empty())
1223 if ((current.
row() + 1) > moduleState()->jobs().
size())
1225 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Unexpected row number" << current.
row() <<
"- ignoring.";
1228 moduleState()->setCurrentPosition(current.
row());
1229 SchedulerJob *
const job = moduleState()->jobs().at(current.
row());
1233 if (jobUnderEdit < 0)
1235 else if (jobUnderEdit != current.
row())
1238 process()->appendLogText(
i18n(
"Stop editing of job #%1, resetting to original value.", jobUnderEdit + 1));
1243 else nightTime->setText(
"-");
1252 handleAltitudeGraph(index.
row());
1256 if (index.
isValid() && index.
row() < moduleState()->jobs().count())
1267 addToQueueB->setToolTip(
i18n(
"Use edition fields to create a new job in the observation list."));
1273 addToQueueB->setToolTip(
i18n(
"Apply job changes."));
1276 checkJobInputComplete();
1283 int const currentRow = moduleState()->currentPosition();
1284 if (currentRow >= 0)
1286 SchedulerJob *currentJob = moduleState()->jobs().at(currentRow);
1288 queueUpB->setEnabled(0 < currentRow &&
1289 (currentJob->isLead() || (currentRow > 1 && moduleState()->findLead(currentRow - 2) !=
nullptr)));
1291 queueDownB->setEnabled(currentRow < queueTable->rowCount() - 1 &&
1292 (moduleState()->findLead(currentRow + 1,
false) !=
nullptr));
1297 queueUpB->setEnabled(
false);
1298 queueDownB->setEnabled(
false);
1300 sortJobsB->setEnabled(can_reorder);
1301 removeFromQueueB->setEnabled(can_delete);
1303 nameEdit->setEnabled(is_lead);
1304 selectObjectB->setEnabled(is_lead);
1305 targetStarLabel->setVisible(is_lead);
1306 raBox->setEnabled(is_lead);
1307 decBox->setEnabled(is_lead);
1308 copySkyCenterB->setEnabled(is_lead);
1309 schedulerProfileCombo->setEnabled(is_lead);
1310 fitsEdit->setEnabled(is_lead);
1311 selectFITSB->setEnabled(is_lead);
1312 groupEdit->setEnabled(is_lead);
1313 schedulerTrackStep->setEnabled(is_lead);
1314 schedulerFocusStep->setEnabled(is_lead);
1315 schedulerAlignStep->setEnabled(is_lead);
1316 schedulerGuideStep->setEnabled(is_lead);
1317 startupGroup->setEnabled(is_lead);
1318 contraintsGroup->setEnabled(is_lead);
1321 leadFollowerSelectionCB->setEnabled(moduleState()->findLead(queueTable->currentRow()) !=
nullptr);
1322 if (leadFollowerSelectionCB->isEnabled() ==
false)
1323 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1329 foreach (SchedulerJob* job, moduleState()->jobs())
1330 if (!reordered_sublist.
contains(job))
1331 reordered_sublist.
append(job);
1333 if (moduleState()->jobs() != reordered_sublist)
1336 int const selectedRow = moduleState()->currentPosition();
1337 SchedulerJob *
const selectedJob = 0 <= selectedRow ? moduleState()->jobs().at(selectedRow) :
nullptr;
1340 moduleState()->setJobs(reordered_sublist);
1343 for (SchedulerJob *job : moduleState()->jobs())
1347 if (
nullptr != selectedJob)
1348 moduleState()->setCurrentPosition(moduleState()->jobs().indexOf(selectedJob));
1357 int const rowCount = queueTable->rowCount();
1358 int const currentRow = queueTable->currentRow();
1360 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1362 if (moduleState()->jobs().at(currentRow)->isLead())
1364 int const rows = 1 + job->followerJobs().
count();
1366 if (currentRow - rows < 0)
1370 destinationRow = currentRow - 1 - moduleState()->jobs().at(currentRow - rows)->followerJobs().count();
1373 destinationRow = currentRow - 1;
1376 if (currentRow < 0 || rowCount <= 1 || destinationRow < 0)
1379 if (moduleState()->jobs().at(currentRow)->isLead())
1382 moduleState()->mutlableJobs().removeOne(job);
1383 for (
auto follower : job->followerJobs())
1384 moduleState()->mutlableJobs().removeOne(follower);
1387 moduleState()->mutlableJobs().insert(destinationRow++, job);
1389 for (
auto follower : job->followerJobs())
1390 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1392 for (
int i = currentRow; i > destinationRow; i--)
1395 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1400#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1401 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1403 moduleState()->jobs().swap(currentRow, destinationRow);
1411 moduleState()->setCurrentPosition(destinationRow);
1413 SchedulerJob *newLead = moduleState()->findLead(destinationRow,
true);
1414 if (newLead !=
nullptr)
1416 job->setLeadJob(newLead);
1417 moduleState()->refreshFollowerLists();
1421 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1424 moduleState()->setDirty(
true);
1425 process()->evaluateJobs(
true);
1430 int const rowCount = queueTable->rowCount();
1431 int const currentRow = queueTable->currentRow();
1433 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1435 if (moduleState()->jobs().at(currentRow)->isLead())
1437 int const rows = 1 + job->followerJobs().
count();
1439 if (currentRow + rows >= moduleState()->jobs().count())
1443 destinationRow = currentRow + 1 + moduleState()->jobs().at(currentRow + rows)->followerJobs().count();
1446 destinationRow = currentRow + 1;
1449 if (currentRow < 0 || rowCount <= 1 || destinationRow >= rowCount)
1452 if (moduleState()->jobs().at(currentRow)->isLead())
1455 moduleState()->mutlableJobs().removeOne(job);
1456 for (
auto follower : job->followerJobs())
1457 moduleState()->mutlableJobs().removeOne(follower);
1460 moduleState()->mutlableJobs().insert(destinationRow++, job);
1462 for (
auto follower : job->followerJobs())
1463 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1465 for (
int i = currentRow; i < destinationRow; i++)
1468 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1473#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1474 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1476 moduleState()->mutlableJobs().swap(currentRow, destinationRow);
1482 moduleState()->setCurrentPosition(destinationRow);
1484 if (moduleState()->jobs().at(currentRow)->isLead())
1486 job->setLeadJob(moduleState()->jobs().at(currentRow));
1487 moduleState()->refreshFollowerLists();
1491 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1494 moduleState()->setDirty(
true);
1495 process()->evaluateJobs(
true);
1503 for (
auto onejob : moduleState()->jobs())
1509 const int row = moduleState()->jobs().indexOf(job);
1514 if (row >= queueTable->rowCount())
1517 QTableWidgetItem *nameCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_NAME));
1518 QTableWidgetItem *statusCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STATUS));
1519 QTableWidgetItem *altitudeCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ALTITUDE));
1520 QTableWidgetItem *startupCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STARTTIME));
1521 QTableWidgetItem *completionCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ENDTIME));
1522 QTableWidgetItem *captureCountCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_CAPTURES));
1525 if (!nameCell)
return;
1527 if (
nullptr != nameCell)
1529 nameCell->
setText(job->isLead() ? job->getName() :
"*");
1535 if (
nullptr != statusCell)
1538 static QString stateStringUnknown;
1549 stateStringUnknown =
i18n(
"Unknown");
1551 statusCell->
setText(stateStrings.
value(job->getState(), stateStringUnknown));
1558 if (
nullptr != startupCell)
1560 auto time = (job->getState() ==
SCHEDJOB_BUSY) ? job->getStateTime() : job->getStartupTime();
1565 .arg(job->getAltitudeAtStartup() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1566 .arg(
QChar(job->isSettingAtStartup() ? 0x2193 : 0x2191))
1567 .
arg(job->getAltitudeAtStartup(), 0,
'f', 1)
1568 .
arg(time.toString(startupTimeEdit->displayFormat())));
1569 job->setStartupFormatted(startupCell->
text());
1571 switch (job->getFileStartupCondition())
1600 if (
nullptr != altitudeCell)
1603 bool is_setting =
false;
1604 double const alt = SchedulerUtils::findAltitude(job->getTargetCoords(),
QDateTime(), &is_setting);
1607 .arg(
QChar(is_setting ? 0x2193 : 0x2191))
1608 .arg(alt, 0,
'f', 1));
1610 job->setAltitudeFormatted(altitudeCell->
text());
1616 if (
nullptr != completionCell)
1619 if (job->getStopTime().
isValid())
1622 .arg(job->getAltitudeAtStop() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1623 .arg(
QChar(job->isSettingAtStop() ? 0x2193 : 0x2191))
1624 .
arg(job->getAltitudeAtStop(), 0,
'f', 1)
1625 .
arg(job->getStopTime().
toString(startupTimeEdit->displayFormat())));
1626 job->setEndFormatted(completionCell->
text());
1628 switch (job->getCompletionCondition())
1634 case FINISH_SEQUENCE:
1653 if (
nullptr != captureCountCell)
1655 switch (job->getCompletionCondition())
1662 captureCountCell->
setText(
QString(
"%L1/-").arg(job->getCompletedCount()));
1665 case FINISH_SEQUENCE:
1669 captureCountCell->
setText(
QString(
"%L1/%L2").arg(job->getCompletedCount()).
arg(job->getSequenceCount()));
1673 QString tooltip = job->getProgressSummary();
1674 if (tooltip.
size() == 0)
1675 tooltip =
i18n(
"Count of captures stored for the job, based on its sequence job.\n"
1676 "This is a summary, additional specific frame types may be required to complete the job.");
1684 m_JobUpdateDebounce.
start();
1689 const int pos = above ? row : row + 1;
1692 if (row > queueTable->rowCount())
1695 queueTable->insertRow(
pos);
1698 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_NAME), nameCell);
1703 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STATUS), statusCell);
1708 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_CAPTURES), captureCount);
1713 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STARTTIME), startupCell);
1718 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ALTITUDE), altitudeCell);
1723 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ENDTIME), completionCell);
1736void Scheduler::resetJobEdit()
1738 if (jobUnderEdit < 0)
1741 SchedulerJob *
const job = moduleState()->jobs().at(jobUnderEdit);
1742 Q_ASSERT_X(job !=
nullptr, __FUNCTION__,
"Edited job must be valid");
1744 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is not longer edited.").
arg(job->getName()).
arg(
1754 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1757 evaluateOnlyB->setEnabled(
true);
1758 startB->setEnabled(
true);
1761 Q_ASSERT_X(jobUnderEdit == -1, __FUNCTION__,
"No more edited/selected job after exiting edit mode");
1766 int currentRow = moduleState()->currentPosition();
1769 if (moduleState()->
removeJob(currentRow) ==
false)
1777 queueTable->removeRow(currentRow);
1780 if (queueTable->rowCount() == 0)
1782 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1783 evaluateOnlyB->setEnabled(
false);
1784 queueSaveAsB->setEnabled(
false);
1785 queueSaveB->setEnabled(
false);
1786 startB->setEnabled(
false);
1787 pauseB->setEnabled(
false);
1794 queueTable->clearSelection();
1797 if (jobUnderEdit >= 0)
1801 moduleState()->refreshFollowerLists();
1802 process()->evaluateJobs(
true);
1805 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1810 moduleState()->setCurrentPosition(index);
1813void Scheduler::toggleScheduler()
1815 if (moduleState()->schedulerState() == SCHEDULER_RUNNING)
1817 moduleState()->disablePreemptiveShutdown();
1824void Scheduler::pause()
1826 moduleState()->setSchedulerState(SCHEDULER_PAUSED);
1827 process()->appendLogText(
i18n(
"Scheduler pause planned..."));
1828 pauseB->setEnabled(
false);
1831 startB->setToolTip(
i18n(
"Resume Scheduler"));
1834void Scheduler::syncGreedyParams()
1836 process()->getGreedyScheduler()->setParams(
1837 errorHandlingRestartImmediatelyButton->isChecked(),
1838 errorHandlingRestartQueueButton->isChecked(),
1839 errorHandlingRescheduleErrorsCB->isChecked(),
1840 errorHandlingStrategyDelay->value(),
1841 errorHandlingStrategyDelay->value());
1844void Scheduler::handleShutdownStarted()
1846 KSNotification::event(
QLatin1String(
"ObservatoryShutdown"),
i18n(
"Observatory is in the shutdown process"),
1847 KSNotification::Scheduler);
1848 weatherLabel->hide();
1851void Ekos::Scheduler::changeSleepLabel(
QString text,
bool show)
1853 sleepLabel->setToolTip(text);
1862 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_NOTHING).toLatin1().data());
1865 bool wasAborted =
false;
1866 for (
auto &oneJob : moduleState()->jobs())
1876 KSNotification::event(
QLatin1String(
"SchedulerAborted"),
i18n(
"Scheduler aborted."), KSNotification::Scheduler,
1877 KSNotification::Alert);
1879 startupB->setEnabled(
true);
1880 shutdownB->setEnabled(
true);
1883 if (moduleState()->preemptiveShutdown())
1885 changeSleepLabel(
i18n(
"Scheduler is in shutdown until next job is ready"));
1890 changeSleepLabel(
"",
false);
1893 startB->setToolTip(
i18n(
"Start Scheduler"));
1894 pauseB->setEnabled(
false);
1897 queueLoadB->setEnabled(
true);
1898 queueAppendB->setEnabled(
true);
1899 addToQueueB->setEnabled(
true);
1900 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1902 evaluateOnlyB->setEnabled(
true);
1908 return load(
true, path.toLocalFile());
1918 "Ekos Scheduler List (*.esl)");
1925 if (fileURL.
isValid() ==
false)
1928 KSNotification::sorry(message,
i18n(
"Invalid URL"));
1935 process()->removeAllJobs();
1937 const int row = moduleState()->jobs().count();
1942 const bool success = process()->appendEkosScheduleList(fileURL.
toLocalFile());
1949 if (moduleState()->jobs().count() > row)
1950 moduleState()->setCurrentPosition(row);
1953 process()->startJobEvaluation();
1963 if (jobUnderEdit >= 0)
1966 while (queueTable->rowCount() > 0)
1967 queueTable->removeRow(0);
1972 process()->clearLog();
1975void Scheduler::saveAs()
1977 schedulerURL.
clear();
1983 QUrl backupCurrent = schedulerURL;
1984 schedulerURL = path;
1990 schedulerURL = backupCurrent;
1995bool Scheduler::save()
1997 QUrl backupCurrent = schedulerURL;
2000 schedulerURL.
clear();
2003 if (moduleState()->dirty() ==
false && !schedulerURL.
isEmpty())
2010 "Ekos Scheduler List (*.esl)");
2014 schedulerURL = backupCurrent;
2026 if ((process()->saveScheduler(schedulerURL)) ==
false)
2028 KSNotification::error(
i18n(
"Failed to save scheduler list"),
i18n(
"Save"));
2033 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.
fileName());
2038 KSNotification::sorry(message,
i18n(
"Invalid URL"));
2045void Scheduler::checkJobInputComplete()
2048 bool const nameSelectionOK = !raBox->isEmpty() && !decBox->isEmpty() && !nameEdit->text().isEmpty();
2051 bool const fitsSelectionOK = !nameEdit->text().isEmpty() && !fitsURL.
isEmpty();
2054 bool const seqSelectionOK = !sequenceEdit->text().isEmpty();
2057 bool const addingOK = (nameSelectionOK || fitsSelectionOK) && seqSelectionOK;
2059 addToQueueB->setEnabled(addingOK);
2065 checkJobInputComplete();
2068 if (jobUnderEdit < 0)
2071 moduleState()->setDirty(
true);
2073 if (
sender() == startupProcedureButtonGroup ||
sender() == shutdownProcedureGroup)
2077 if (
sender() == schedulerStartupScript)
2079 else if (
sender() == schedulerShutdownScript)
2080 moduleState()->setShutdownScriptURL(
QUrl::fromUserInput(schedulerShutdownScript->text()));
2086 if (moduleState()->jobs().isEmpty())
2095 using namespace std::placeholders;
2097 std::stable_sort(sortedJobs.
begin() + 1, sortedJobs.
end(),
2098 std::bind(SchedulerJob::decreasingAltitudeOrder, _1, _2, moduleState()->jobs().first()->getStartupTime()));
2103 for (SchedulerJob * job : moduleState()->jobs())
2106 process()->evaluateJobs(
true);
2113 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_SCHEDULER).toLatin1().data());
2114 moduleState()->setupNextIteration(RUN_SCHEDULER);
2120 if (errorHandlingRestartQueueButton->isChecked())
2121 return ERROR_RESTART_AFTER_TERMINATION;
2122 else if (errorHandlingRestartImmediatelyButton->isChecked())
2123 return ERROR_RESTART_IMMEDIATELY;
2125 return ERROR_DONT_RESTART;
2130 errorHandlingStrategyDelay->setEnabled(strategy != ERROR_DONT_RESTART);
2134 case ERROR_RESTART_AFTER_TERMINATION:
2135 errorHandlingRestartQueueButton->setChecked(
true);
2137 case ERROR_RESTART_IMMEDIATELY:
2138 errorHandlingRestartImmediatelyButton->setChecked(
true);
2141 errorHandlingDontRestartButton->setChecked(
true);
2149void Scheduler::setAlgorithm(
int algIndex)
2151 if (algIndex != ALGORITHM_GREEDY)
2153 process()->appendLogText(
2154 i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
2155 algIndex = ALGORITHM_GREEDY;
2157 Options::setSchedulerAlgorithm(algIndex);
2159 groupLabel->setDisabled(
false);
2160 groupEdit->setDisabled(
false);
2161 queueTable->model()->setHeaderData(START_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next Start"));
2162 queueTable->model()->setHeaderData(END_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next End"));
2171 process()->appendLogText(
2172 i18n(
"Turning off astronomical twilight check may cause the observatory to run during daylight. This can cause irreversible damage to your equipment!"));
2176void Scheduler::updateProfiles()
2178 schedulerProfileCombo->blockSignals(
true);
2179 schedulerProfileCombo->clear();
2180 schedulerProfileCombo->addItems(moduleState()->profiles());
2181 schedulerProfileCombo->setCurrentText(moduleState()->currentProfile());
2182 schedulerProfileCombo->blockSignals(
false);
2185void Scheduler::updateJobStageUI(SchedulerJobStage stage)
2190 static QString stageStringUnknown;
2193 stageStrings[SCHEDSTAGE_IDLE] =
i18n(
"Idle");
2194 stageStrings[SCHEDSTAGE_SLEWING] =
i18n(
"Slewing");
2195 stageStrings[SCHEDSTAGE_SLEW_COMPLETE] =
i18n(
"Slew complete");
2196 stageStrings[SCHEDSTAGE_FOCUSING] =
2197 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING] =
i18n(
"Focusing");
2198 stageStrings[SCHEDSTAGE_FOCUS_COMPLETE] =
2199 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING_COMPLETE ] =
i18n(
"Focus complete");
2200 stageStrings[SCHEDSTAGE_ALIGNING] =
i18n(
"Aligning");
2201 stageStrings[SCHEDSTAGE_ALIGN_COMPLETE] =
i18n(
"Align complete");
2202 stageStrings[SCHEDSTAGE_RESLEWING] =
i18n(
"Repositioning");
2203 stageStrings[SCHEDSTAGE_RESLEWING_COMPLETE] =
i18n(
"Repositioning complete");
2205 stageStrings[SCHEDSTAGE_GUIDING] =
i18n(
"Guiding");
2206 stageStrings[SCHEDSTAGE_GUIDING_COMPLETE] =
i18n(
"Guiding complete");
2207 stageStrings[SCHEDSTAGE_CAPTURING] =
i18n(
"Capturing");
2208 stageStringUnknown =
i18n(
"Unknown");
2211 if (activeJob() ==
nullptr)
2212 jobStatus->setText(stageStrings[SCHEDSTAGE_IDLE]);
2214 jobStatus->setText(
QString(
"%1: %2").arg(activeJob()->getName(),
2215 stageStrings.
value(stage, stageStringUnknown)));
2221 if (iface == process()->mountInterface())
2223 QVariant canMountPark = process()->mountInterface()->property(
"canPark");
2226 schedulerUnparkMount->setEnabled(canMountPark.
toBool());
2227 schedulerParkMount->setEnabled(canMountPark.
toBool());
2230 else if (iface == process()->capInterface())
2232 QVariant canCapPark = process()->capInterface()->property(
"canPark");
2235 schedulerCloseDustCover->setEnabled(canCapPark.
toBool());
2236 schedulerOpenDustCover->setEnabled(canCapPark.
toBool());
2240 schedulerCloseDustCover->setEnabled(
false);
2241 schedulerOpenDustCover->setEnabled(
false);
2244 else if (iface == process()->weatherInterface())
2246 QVariant status = process()->weatherInterface()->property(
"status");
2247 if (status.isValid())
2252 schedulerWeather->setEnabled(
true);
2255 schedulerWeather->setEnabled(
false);
2257 else if (iface == process()->domeInterface())
2259 QVariant canDomePark = process()->domeInterface()->property(
"canPark");
2262 schedulerUnparkDome->setEnabled(canDomePark.
toBool());
2263 schedulerParkDome->setEnabled(canDomePark.
toBool());
2266 else if (iface == process()->captureInterface())
2268 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
2269 if (hasCoolerControl.
isValid())
2271 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
2276void Scheduler::setWeatherStatus(ISD::Weather::Status status)
2278 TEST_PRINT(stderr,
"sch%d @@@setWeatherStatus(%d)\n", __LINE__,
static_cast<int>(status));
2279 ISD::Weather::Status newStatus = status;
2284 case ISD::Weather::WEATHER_OK:
2285 statusString =
i18n(
"Weather conditions are OK.");
2288 case ISD::Weather::WEATHER_WARNING:
2289 statusString =
i18n(
"Warning: weather conditions are in the WARNING zone.");
2292 case ISD::Weather::WEATHER_ALERT:
2293 statusString =
i18n(
"Caution: weather conditions are in the DANGER zone!");
2300 qCDebug(KSTARS_EKOS_SCHEDULER) << statusString;
2302 if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_OK)
2303 weatherLabel->setPixmap(
2305 .pixmap(
QSize(32, 32)));
2306 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_WARNING)
2308 weatherLabel->setPixmap(
2310 .pixmap(
QSize(32, 32)));
2311 KSNotification::event(
QLatin1String(
"WeatherWarning"),
i18n(
"Weather conditions in warning zone"),
2312 KSNotification::Scheduler, KSNotification::Warn);
2314 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_ALERT)
2316 weatherLabel->setPixmap(
2318 .pixmap(
QSize(32, 32)));
2320 i18n(
"Weather conditions are critical. Observatory shutdown is imminent"), KSNotification::Scheduler,
2321 KSNotification::Alert);
2325 .pixmap(
QSize(32, 32)));
2327 weatherLabel->show();
2328 weatherLabel->setToolTip(statusString);
2330 process()->appendLogText(statusString);
2332 emit weatherChanged(moduleState()->weatherStatus());
2339 schedulerWeather->setEnabled(
false);
2340 weatherLabel->hide();
2343 changeSleepLabel(
i18n(
"Scheduler is in sleep mode"));
2350 case SCHEDULER_RUNNING:
2355 startB->setToolTip(
i18n(
"Stop Scheduler"));
2356 pauseB->setEnabled(
true);
2357 pauseB->setChecked(
false);
2360 queueLoadB->setEnabled(
false);
2361 setJobManipulation(
true,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
2363 evaluateOnlyB->setEnabled(
false);
2364 startupB->setEnabled(
false);
2365 shutdownB->setEnabled(
false);
2372 emit newStatus(newState);
2377 pauseB->setCheckable(
true);
2378 pauseB->setChecked(
true);
2381void Scheduler::handleJobsUpdated(
QJsonArray jobsList)
2386 emit jobsUpdated(jobsList);
2392 return assistant->importMosaic(payload);
2395void Scheduler::startupStateChanged(StartupState state)
2397 jobStatus->setText(startupStateString(state));
2399 switch (moduleState()->startupState())
2404 case STARTUP_COMPLETE:
2406 process()->appendLogText(
i18n(
"Manual startup procedure completed successfully."));
2410 process()->appendLogText(
i18n(
"Manual startup procedure terminated due to errors."));
2418void Scheduler::shutdownStateChanged(ShutdownState state)
2420 if (state == SHUTDOWN_COMPLETE || state == SHUTDOWN_IDLE
2421 || state == SHUTDOWN_ERROR)
2429 if (state == SHUTDOWN_IDLE)
2430 jobStatus->setText(
i18n(
"Idle"));
2432 jobStatus->setText(shutdownStateString(state));
2434void Scheduler::ekosStateChanged(EkosState state)
2436 if (state == EKOS_IDLE)
2438 jobStatus->setText(
i18n(
"Idle"));
2442 jobStatus->setText(ekosStateString(state));
2444void Scheduler::indiStateChanged(INDIState state)
2446 if (state == INDI_IDLE)
2448 jobStatus->setText(
i18n(
"Idle"));
2452 jobStatus->setText(indiStateString(state));
2454 refreshOpticalTrain();
2457void Scheduler::indiCommunicationStatusChanged(CommunicationStatus status)
2459 if (status == Success)
2460 refreshOpticalTrain();
2462void Scheduler::parkWaitStateChanged(ParkWaitState state)
2464 jobStatus->setText(parkWaitStateString(state));
2467SchedulerJob *Scheduler::activeJob()
2469 return moduleState()->activeJob();
2472void Scheduler::loadGlobalSettings()
2477 QVariantMap settings;
2481 key = oneWidget->objectName();
2482 value = Options::self()->property(key.
toLatin1());
2483 if (value.
isValid() && oneWidget->count() > 0)
2485 oneWidget->setCurrentText(value.
toString());
2486 settings[key] = value;
2489 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2495 key = oneWidget->objectName();
2496 value = Options::self()->property(key.
toLatin1());
2499 oneWidget->setValue(value.
toDouble());
2500 settings[key] = value;
2503 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2509 key = oneWidget->objectName();
2510 value = Options::self()->property(key.
toLatin1());
2513 oneWidget->setValue(value.
toInt());
2514 settings[key] = value;
2517 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2523 key = oneWidget->objectName();
2524 value = Options::self()->property(key.
toLatin1());
2527 oneWidget->setChecked(value.
toBool());
2528 settings[key] = value;
2531 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2537 key = oneWidget->objectName();
2538 value = Options::self()->property(key.
toLatin1());
2541 oneWidget->setText(value.
toString());
2542 settings[key] = value;
2544 if (key ==
"sequenceEdit")
2546 else if (key ==
"schedulerStartupScript")
2548 else if (key ==
"schedulerShutdownScript")
2552 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2558 key = oneWidget->objectName();
2559 value = Options::self()->property(key.
toLatin1());
2562 oneWidget->setChecked(value.
toBool());
2563 settings[key] = value;
2570 key = oneWidget->objectName();
2571 value = Options::self()->property(key.
toLatin1());
2575 settings[key] = value;
2581 m_GlobalSettings = m_Settings = settings;
2584void Scheduler::syncSettings()
2600 value = dsb->
value();
2606 value = sb->
value();
2618 m_Settings.remove(key);
2631 value = lineedit->
text();
2640 Options::self()->setProperty(key.
toLatin1(), value);
2642 m_Settings[key] = value;
2643 m_GlobalSettings[key] = value;
2645 emit settingsUpdated(getAllSettings());
2651QVariantMap Scheduler::getAllSettings()
const
2653 QVariantMap settings;
2657 settings.insert(oneWidget->objectName(), oneWidget->currentText());
2661 settings.insert(oneWidget->objectName(), oneWidget->value());
2665 settings.insert(oneWidget->objectName(), oneWidget->value());
2669 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2675 if (!oneWidget->objectName().startsWith(
"qt_"))
2676 settings.insert(oneWidget->objectName(), oneWidget->text());
2681 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2686 settings.insert(oneWidget->objectName(), oneWidget->dateTime().toString(
Qt::ISODate));
2695void Scheduler::setAllSettings(
const QVariantMap &settings)
2699 disconnectSettings();
2701 for (
auto &name : settings.keys())
2707 syncControl(settings, name, comboBox);
2715 syncControl(settings, name, doubleSpinBox);
2723 syncControl(settings, name, spinBox);
2731 syncControl(settings, name, checkbox);
2739 syncControl(settings, name, lineedit);
2741 if (name ==
"sequenceEdit")
2743 else if (name ==
"fitsEdit")
2745 else if (name ==
"schedulerStartupScript")
2747 else if (name ==
"schedulerShutdownScript")
2757 syncControl(settings, name, radioButton);
2764 syncControl(settings, name, datetimeedit);
2769 m_Settings = settings;
2778bool Scheduler::syncControl(
const QVariantMap &settings,
const QString &key,
QWidget * widget)
2791 const int value = settings[key].toInt(&ok);
2800 const double value = settings[key].toDouble(&ok);
2809 const bool value = settings[key].toBool();
2817 const QString value = settings[key].toString();
2823 const auto value = settings[key].toString();
2829 const bool value = settings[key].toBool();
2831 pRadioButton->
click();
2844void Scheduler::refreshOpticalTrain()
2846 opticalTrainCombo->blockSignals(
true);
2847 opticalTrainCombo->clear();
2848 opticalTrainCombo->addItem(
"--");
2849 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
2850 opticalTrainCombo->blockSignals(
false);
2853void Scheduler::connectSettings()
2879 if (!oneWidget->objectName().startsWith(
"qt_"))
2888void Scheduler::disconnectSettings()
2919void Scheduler::handleAltitudeGraph(
int index)
2921 if (!m_altitudeGraph)
2922 m_altitudeGraph =
new SchedulerAltitudeGraph;
2924 if (index < 0 || index >= moduleState()->jobs().
size())
2926 auto job = moduleState()->jobs().at(index);
2928 QDateTime now = SchedulerModuleState::getLocalTime(), start,
end;
2930 SchedulerModuleState::calculateDawnDusk(now, nextDawn, nextDusk);
2933 QDateTime plotStart = (nextDusk < nextDawn) ? nextDusk : nextDusk.addDays(-1);
2942 plotStart = plotStart.
addSecs(-1 * 3600);
2944 auto plotEnd = nextDawn.
addSecs(1 * 3600);
2945 while (t.secsTo(plotEnd) > 0)
2947 double alt = SchedulerUtils::findAltitude(job->getTargetCoords(), t);
2949 double hour = midnight.
secsTo(t) / 3600.0;
2951 t = t.addSecs(60 * 10);
2955 KSAlmanac ksal(ut, SchedulerModuleState::getGeo());
2956 m_altitudeGraph->setTitle(job->getName());
2957 m_altitudeGraph->plot(SchedulerModuleState::getGeo(), &ksal, times, alts);
2960 auto startTime = (job->getState() ==
SCHEDJOB_BUSY) ? job->getStateTime() : job->getStartupTime();
2961 if (startTime.isValid() && startTime < plotEnd && job->getStopTime().
isValid())
2963 auto stopTime = job->getStopTime();
2964 if (startTime < plotStart) startTime = plotStart;
2965 if (stopTime > plotEnd)
2970 while (t.secsTo(stopTime) > 0)
2972 double alt = SchedulerUtils::findAltitude(job->getTargetCoords(), t);
2974 double hour = midnight.
secsTo(t) / 3600.0;
2976 t = t.addSecs(60 * 10);
2979 m_altitudeGraph->plot(SchedulerModuleState::getGeo(), &ksal, runTimes, runAlts,
true);
2981 m_altitudeGraph->show();
The SchedulerProcess class holds the entire business logic for controlling the execution of the EKOS ...
Q_SCRIPTABLE Q_NOREPLY void runStartupProcedure()
runStartupProcedure Execute the startup of the scheduler itself to be prepared for running scheduler ...
Q_SCRIPTABLE Q_NOREPLY void startJobEvaluation()
startJobEvaluation Start job evaluation only without starting the scheduler process itself.
Q_SCRIPTABLE Q_NOREPLY void runShutdownProcedure()
runShutdownProcedure Shutdown the scheduler itself and EKOS (if configured to do so).
ErrorHandlingStrategy getErrorHandlingStrategy()
retrieve the error handling strategy from the UI
void moveJobUp()
moveJobUp Move the selected job up in the job list.
void watchJobChanges(bool enable)
Q_INVOKABLE void clearLog()
clearLog Clears log entry
void checkTwilightWarning(bool enabled)
checkWeather Check weather status and act accordingly depending on the current status of the schedule...
void saveJob(SchedulerJob *job=nullptr)
addToQueue Construct a SchedulerJob and add it to the queue or save job settings from current form va...
void setJobManipulation(bool can_reorder, bool can_delete, bool is_lead)
setJobManipulation Enable or disable job manipulation buttons.
void updateSchedulerURL(const QString &fileURL)
updateSchedulerURL Update scheduler URL after succesful loading a new file.
Q_INVOKABLE void addJob(SchedulerJob *job=nullptr)
addJob Add a new job from form values
void selectSequence()
Selects sequence queue.
void insertJobTableRow(int row, bool above=true)
insertJobTableRow Insert a new row (empty) into the job table
Q_INVOKABLE bool load(bool clearQueue, const QString &filename=QString())
load Open a file dialog to select an ESL file, and load its contents.
void resumeCheckStatus()
resumeCheckStatus If the scheduler primary loop was suspended due to weather or sleep event,...
void handleSchedulerSleeping(bool shutdown, bool sleep)
handleSchedulerSleeping Update UI if scheduler is set to sleep
void moveJobDown()
moveJobDown Move the selected job down in the list.
bool importMosaic(const QJsonObject &payload)
importMosaic Import mosaic into planner and generate jobs for the scheduler.
void handleSetPaused()
handleSetPaused Update the UI when {
bool reorderJobs(QList< SchedulerJob * > reordered_sublist)
reorderJobs Change the order of jobs in the UI based on a subset of its jobs.
void syncGUIToGeneralSettings()
syncGUIToGeneralSettings set all UI fields that are not job specific
void updateNightTime(SchedulerJob const *job=nullptr)
updateNightTime update the Twilight restriction with the argument job properties.
bool loadFile(const QUrl &path)
loadFile Load scheduler jobs from disk
void handleSchedulerStateChanged(SchedulerState newState)
handleSchedulerStateChanged Update UI when the scheduler state changes
bool fillJobFromUI(SchedulerJob *job)
createJob Create a new job from form values.
Q_INVOKABLE void loadJob(QModelIndex i)
editJob Edit an observation job
void setSequence(const QString &sequenceFileURL)
Set the file URL pointing to the capture sequence file.
Q_INVOKABLE void updateJob(int index=-1)
addJob Add a new job from form values
void selectStartupScript()
Selects sequence queue.
void syncGUIToJob(SchedulerJob *job)
set all GUI fields to the values of the given scheduler job
void schedulerStopped()
schedulerStopped React when the process engine has stopped the scheduler
void selectObject()
select object from KStars's find dialog.
void updateCellStyle(SchedulerJob *job, QTableWidgetItem *cell)
Update the style of a cell, depending on the job's state.
Q_INVOKABLE void clearJobTable()
clearJobTable delete all rows in the job table
void setJobAddApply(bool add_mode)
setJobAddApply Set first button state to add new job or apply changes.
void handleConfigChanged()
handleConfigChanged Update UI after changes to the global configuration
bool saveFile(const QUrl &path)
saveFile Save scheduler jobs to disk
Q_SCRIPTABLE void sortJobsPerAltitude()
DBUS interface function.
void setErrorHandlingStrategy(ErrorHandlingStrategy strategy)
select the error handling strategy (no restart, restart after all terminated, restart immediately)
void clickQueueTable(QModelIndex index)
jobSelectionChanged Update UI state when the job list is clicked once.
void updateJobTable(SchedulerJob *job=nullptr)
updateJobTable Update the job's row in the job table.
void removeJob()
Remove a job from current table row.
void removeOneJob(int index)
Remove a job by selecting a table row.
void selectFITS()
Selects FITS file for solving.
Scheduler()
Constructor, the starndard scheduler constructor.
void interfaceReady(QDBusInterface *iface)
checkInterfaceReady Sometimes syncProperties() is not sufficient since the ready signal could have fi...
void queueTableSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
Update scheduler parameters to the currently selected scheduler job.
void selectShutdownScript()
Selects sequence queue.
SkyObject * targetObject()
Q_INVOKABLE QAction * action(const QString &name) const
static KConfigDialog * exists(const QString &name)
void settingsChanged(const QString &dialogName)
A class that implements methods to find sun rise, sun set, twilight begin / end times,...
const KStarsDateTime & ut() const
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
static KStars * Instance()
virtual KActionCollection * actionCollection() const
The QProgressIndicator class lets an application display a progress indicator to show that a long tas...
void stopAnimation()
Stops the spin animation.
void startAnimation()
Starts the spin animation.
The SchedulerState class holds all attributes defining the scheduler's state.
Provides all necessary information about an object in the sky: its coordinates, name(s),...
virtual QString name(void) const
The sky coordinates of a point in the sky.
const CachingDms & ra0() const
const CachingDms & dec0() const
This is a subclass of SkyObject.
An angle, stored as degrees, but expressible in many ways.
static dms fromString(const QString &s, bool deg)
Static function to create a DMS object from a QString.
virtual void setD(const double &x)
Sets floating-point value of angle, in degrees.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
StartupCondition
Conditions under which a SchedulerJob may start.
@ 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.
ErrorHandlingStrategy
options what should happen if an error or abort occurs
CompletionCondition
Conditions under which a SchedulerJob may complete.
bool isValid(QStringView ifopt)
const QList< QKeySequence > & end()
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
void clicked(const QModelIndex &index)
void doubleClicked(const QModelIndex &index)
void rangeChanged(int min, int max)
void valueChanged(int value)
void triggered(bool checked)
void activated(int index)
void currentIndexChanged(int index)
QDate addDays(qint64 ndays) const const
QDateTime addSecs(qint64 s) const const
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isValid() const const
qint64 secsTo(const QDateTime &other) const const
QString toString(QStringView format, QCalendar cal) const const
void dateTimeChanged(const QDateTime &datetime)
void valueChanged(double d)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
QUrl getOpenFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, Options options, const QStringList &supportedSchemes)
QUrl getSaveFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, Options options, const QStringList &supportedSchemes)
void setItalic(bool enable)
Qt::KeyboardModifiers keyboardModifiers()
QIcon fromTheme(const QString &name)
QModelIndexList indexes() const const
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
void textChanged(const QString &text)
void append(QList< T > &&value)
bool contains(const AT &value) const const
qsizetype count() const const
void push_back(parameter_type value)
bool isEmpty() const const
T value(const Key &key, const T &defaultValue) const const
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
T findChild(const QString &name, Qt::FindChildOptions options) const const
QList< T > findChildren(Qt::FindChildOptions options) const const
T qobject_cast(QObject *object)
QObject * sender() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
void setFont(const QFont &font)
void appendRow(QStandardItem *item)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QTextStream & center(QTextStream &stream)
void resizeColumnToContents(int column)
void setText(const QString &text)
void setTextAlignment(Qt::Alignment alignment)
QString text() const const
bool setHMS(int h, int m, int s, int ms)
void setInterval(int msec)
void setSingleShot(bool singleShot)
QString fileName(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
bool isEmpty() const const
bool isValid() const const
void setPath(const QString &path, ParsingMode mode)
QString toLocalFile() const const
QString url(FormattingOptions options) const const
bool isValid() const const
bool toBool() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QString toString() const const