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)
104 if (kstarsInterfaceString ==
"org.kde.kstars")
107 qRegisterMetaType<Ekos::SchedulerState>(
"Ekos::SchedulerState");
108 qDBusRegisterMetaType<Ekos::SchedulerState>();
110 m_moduleState.
reset(
new SchedulerModuleState());
111 m_process.reset(
new SchedulerProcess(moduleState(), ekosPathStr, ekosInterfaceStr));
116 QDateTime currentDateTime = SchedulerModuleState::getLocalTime();
117 QTime currentTime = currentDateTime.
time();
119 currentDateTime.
setTime(currentTime);
122 startupTimeEdit->setDateTime(currentDateTime);
123 schedulerUntilValue->setDateTime(currentDateTime);
134 leadFollowerSelectionCB->setModel(model);
136 sleepLabel->setPixmap(
138 changeSleepLabel(
"",
false);
141 bottomLayout->addWidget(pi, 0);
143 geo = KStarsData::Instance()->geo();
146 raBox->setUnits(dmsBox::HOURS);
155 queueTable->setToolTip(
156 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."));
157 QTableWidgetItem *statusHeader = queueTable->horizontalHeaderItem(SCHEDCOL_STATUS);
158 QTableWidgetItem *altitudeHeader = queueTable->horizontalHeaderItem(SCHEDCOL_ALTITUDE);
159 QTableWidgetItem *startupHeader = queueTable->horizontalHeaderItem(SCHEDCOL_STARTTIME);
160 QTableWidgetItem *completionHeader = queueTable->horizontalHeaderItem(SCHEDCOL_ENDTIME);
161 QTableWidgetItem *captureCountHeader = queueTable->horizontalHeaderItem(SCHEDCOL_CAPTURES);
163 if (statusHeader !=
nullptr)
164 statusHeader->
setToolTip(
i18n(
"Current status of the job, managed by the Scheduler.\n"
165 "If invalid, the Scheduler was not able to find a proper observation time for the target.\n"
166 "If aborted, the Scheduler missed the scheduled time or encountered transitory issues and will reschedule the job.\n"
167 "If complete, the Scheduler verified that all sequence captures requested were stored, including repeats."));
168 if (altitudeHeader !=
nullptr)
169 altitudeHeader->
setToolTip(
i18n(
"Current altitude of the target of the job.\n"
170 "A rising target is indicated with an arrow going up.\n"
171 "A setting target is indicated with an arrow going down."));
172 if (startupHeader !=
nullptr)
173 startupHeader->
setToolTip(
i18n(
"Startup time of the job, as estimated by the Scheduler.\n"
174 "The altitude at startup, if available, is displayed too.\n"
175 "Fixed time from user or culmination time is marked with a chronometer symbol."));
176 if (completionHeader !=
nullptr)
177 completionHeader->
setToolTip(
i18n(
"Completion time for the job, as estimated by the Scheduler.\n"
178 "You may specify a fixed time to limit duration of looping jobs. "
179 "A warning symbol indicates the altitude at completion may cause the job to abort before completion.\n"));
180 if (captureCountHeader !=
nullptr)
181 captureCountHeader->
setToolTip(
i18n(
"Count of captures stored for the job, based on its sequence job.\n"
182 "This is a summary, additional specific frame types may be required to complete the job."));
188 removeFromQueueB->setToolTip(
189 i18n(
"Remove selected job from the observation list.\nJob properties are copied in the edition fields before removal."));
193 queueUpB->setToolTip(
i18n(
"Move selected job one line up in the list.\n"));
196 queueDownB->setToolTip(
i18n(
"Move selected job one line down in the list.\n"));
200 evaluateOnlyB->setToolTip(
i18n(
"Reset state and force reevaluation of all observation jobs."));
203 sortJobsB->setToolTip(
204 i18n(
"Reset state and sort observation jobs per altitude and movement in sky, using the start time of the first job.\n"
205 "This action sorts setting targets before rising targets, and may help scheduling when starting your observation.\n"
206 "Note the algorithm first calculates all altitudes using the same time, then evaluates jobs."));
211 positionAngleSpin->setSpecialValueText(
"--");
226 selectShutdownScriptB->setIcon(
241 schedulerRepeatEverything->setEnabled(Options::rememberJobProgress() ==
false);
242 executionSequenceLimit->setEnabled(Options::rememberJobProgress() ==
false);
243 executionSequenceLimit->setValue(Options::schedulerExecutionSequencesLimit());
246 leadFollowerSelectionCB->setEnabled(
false);
256 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated,
this, &Scheduler::refreshOpticalTrain);
260 mosaicB->setDown(checked);
295 pauseB->setCheckable(
false);
314 connect(moduleState().data(), &SchedulerModuleState::ekosStateChanged,
this, &Scheduler::ekosStateChanged);
315 connect(moduleState().data(), &SchedulerModuleState::indiStateChanged,
this, &Scheduler::indiStateChanged);
316 connect(moduleState().data(), &SchedulerModuleState::indiCommunicationStatusChanged,
this,
317 &Scheduler::indiCommunicationStatusChanged);
319 connect(moduleState().data(), &SchedulerModuleState::startupStateChanged,
this, &Scheduler::startupStateChanged);
320 connect(moduleState().data(), &SchedulerModuleState::shutdownStateChanged,
this, &Scheduler::shutdownStateChanged);
321 connect(moduleState().data(), &SchedulerModuleState::parkWaitStateChanged,
this, &Scheduler::parkWaitStateChanged);
322 connect(moduleState().data(), &SchedulerModuleState::profilesChanged,
this, &Scheduler::updateProfiles);
324 connect(moduleState().data(), &SchedulerModuleState::jobStageChanged,
this, &Scheduler::updateJobStageUI);
326 connect(moduleState().data(), &SchedulerModuleState::currentProfileChanged,
this, [&]()
328 schedulerProfileCombo->setCurrentText(moduleState()->currentProfile());
334 connect(process().data(), &SchedulerProcess::shutdownStarted,
this, &Scheduler::handleShutdownStarted);
336 connect(process().data(), &SchedulerProcess::jobsUpdated,
this, &Scheduler::handleJobsUpdated);
337 connect(process().data(), &SchedulerProcess::targetDistance,
this, &Scheduler::targetDistance);
342 connect(process().data(), &SchedulerProcess::jobStarted,
this, &Scheduler::jobStarted);
343 connect(process().data(), &SchedulerProcess::jobEnded,
this, &Scheduler::jobEnded);
344 connect(process().data(), &SchedulerProcess::syncGreedyParams,
this, &Scheduler::syncGreedyParams);
346 connect(process().data(), &SchedulerProcess::changeSleepLabel,
this, &Scheduler::changeSleepLabel);
349 connect(process().data(), &SchedulerProcess::newWeatherStatus,
this, &Scheduler::setWeatherStatus);
357 connect(errorHandlingButtonGroup,
static_cast<void (QButtonGroup::*)(QAbstractButton *)
>
362 Options::setErrorHandlingStrategy(strategy);
363 errorHandlingStrategyDelay->setEnabled(strategy != ERROR_DONT_RESTART);
367 Options::setErrorHandlingStrategyDelay(value);
371 if (Options::schedulerAlgorithm() != ALGORITHM_GREEDY)
373 process()->appendLogText(
374 i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
375 Options::setSchedulerAlgorithm(ALGORITHM_GREEDY);
379 setAlgorithm(Options::schedulerAlgorithm());
383 SkyPoint
center = SkyMap::Instance()->getCenterPoint();
385 center.catalogueCoord(KStarsData::Instance()->updateNum()->julianDay());
386 raBox->show(
center.ra0());
387 decBox->show(
center.dec0());
392 if (!m_SequenceEditor)
393 m_SequenceEditor.reset(
new SequenceEditor(
this));
395 m_SequenceEditor->show();
396 m_SequenceEditor->raise();
399 m_JobUpdateDebounce.setSingleShot(
true);
400 m_JobUpdateDebounce.setInterval(1000);
403 emit jobsUpdated(moduleState()->getJSONJobs());
406 moduleState()->calculateDawnDusk();
407 process()->loadProfiles();
411 loadGlobalSettings();
413 refreshOpticalTrain();
416QString Scheduler::getCurrentJobName()
418 return (activeJob() !=
nullptr ? activeJob()->getName() :
"");
429 m_OpsOffsetSettings =
new OpsOffsetSettings();
433 m_OpsAlignmentSettings =
new OpsAlignmentSettings();
434 page = dialog->
addPage(m_OpsAlignmentSettings,
i18n(
"Alignment"));
437 m_OpsJobsSettings =
new OpsJobsSettings();
438 page = dialog->
addPage(m_OpsJobsSettings,
i18n(
"Jobs"));
441 m_OpsScriptsSettings =
new OpsScriptsSettings();
442 page = dialog->
addPage(m_OpsScriptsSettings,
i18n(
"Scripts"));
448 if (enable == jobChangesAreWatched)
460 schedulerStartupScript,
461 schedulerShutdownScript
472 schedulerProfileCombo,
474 leadFollowerSelectionCB
480 errorHandlingButtonGroup,
482 constraintButtonGroup,
483 completionButtonGroup,
484 startupProcedureButtonGroup,
485 shutdownProcedureGroup
490 errorHandlingRescheduleErrorsCB,
491 schedulerMoonSeparation,
492 schedulerMoonAltitude,
500 schedulerExecutionSequencesLimit,
501 errorHandlingStrategyDelay
506 schedulerMoonSeparationValue,
507 schedulerMoonAltitudeMaxValue,
508 schedulerAltitudeValue,
523 for (
auto *
const control : lineEdits)
528 for (
auto *
const control : dateEdits)
533 for (
auto *
const control : comboBoxes)
535 if (control == leadFollowerSelectionCB)
537 this, [
this](
int pos)
539 setJobManipulation(queueUpB->isEnabled() || queueDownB->isEnabled(), removeFromQueueB->isEnabled(),
pos == INDEX_LEAD);
548 for (
auto *
const control : buttonGroups)
549#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
557 for (
auto *
const control : buttons)
562 for (
auto *
const control : spinBoxes)
567 for (
auto *
const control : dspinBoxes)
580 for (
auto *
const control : lineEdits)
582 for (
auto *
const control : dateEdits)
584 for (
auto *
const control : comboBoxes)
586 for (
auto *
const control : buttons)
588 for (
auto *
const control : buttonGroups)
589#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
594 for (
auto *
const control : spinBoxes)
596 for (
auto *
const control : dspinBoxes)
600 jobChangesAreWatched = enable;
605 schedulerRepeatEverything->
setEnabled(Options::rememberJobProgress() ==
false);
606 executionSequenceLimit->setEnabled(Options::rememberJobProgress() ==
false);
611 if (FindDialog::Instance()->execWithParent(Ekos::Manager::Instance()) ==
QDialog::Accepted)
613 SkyObject *
object = FindDialog::Instance()->targetObject();
618void Scheduler::addObject(
SkyObject *
object)
620 if (
object !=
nullptr)
624 if (object->
name() ==
"star")
632 nameEdit->setText(finalObjectName);
633 raBox->show(object->
ra0());
634 decBox->show(object->
dec0());
643 "FITS (*.fits *.fit);;XISF (*.xisf)");
647 processFITSSelection(url);
650void Scheduler::processFITSSelection(
const QUrl &url)
660 const QString filename = fitsEdit->text();
662 double ra = 0, dec = 0;
664 char comment[128], error_status[512];
665 fitsfile *fptr =
nullptr;
667 if (fits_open_diskfile(&fptr, filename.
toLatin1(), READONLY, &status))
669 fits_report_error(stderr, status);
670 fits_get_errstatus(status, error_status);
676 if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status))
678 fits_report_error(stderr, status);
679 fits_get_errstatus(status, error_status);
685 char objectra_str[32] = {0};
686 if (fits_read_key(fptr, TSTRING,
"OBJCTRA", objectra_str, comment, &status))
688 if (fits_read_key(fptr, TDOUBLE,
"RA", &ra, comment, &status))
690 fits_report_error(stderr, status);
691 fits_get_errstatus(status, error_status);
692 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTRA (%1).", QString(error_status)));
704 char objectde_str[32] = {0};
705 if (fits_read_key(fptr, TSTRING,
"OBJCTDEC", objectde_str, comment, &status))
707 if (fits_read_key(fptr, TDOUBLE,
"DEC", &dec, comment, &status))
709 fits_report_error(stderr, status);
710 fits_get_errstatus(status, error_status);
711 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTDEC (%1).", QString(error_status)));
725 char object_str[256] = {0};
726 if (fits_read_key(fptr, TSTRING,
"OBJECT", object_str, comment, &status))
728 QFileInfo info(filename);
729 nameEdit->setText(info.completeBaseName());
733 nameEdit->setText(object_str);
745 sequenceEdit->setText(sequenceURL.toLocalFile());
753 dirPath.toLocalFile(),
754 i18n(
"Ekos Sequence Queue (*.esq)"));
762 "Select Startup Script"),
764 i18n(
"Script (*)")));
765 if (moduleState()->startupScriptURL().isEmpty())
770 moduleState()->setDirty(
true);
771 schedulerStartupScript->setText(moduleState()->startupScriptURL().toLocalFile());
777 "Select Shutdown Script"),
779 i18n(
"Script (*)")));
780 if (moduleState()->shutdownScriptURL().isEmpty())
785 moduleState()->setDirty(
true);
786 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toLocalFile());
791 if (0 <= jobUnderEdit)
794 job = moduleState()->jobs().at(jobUnderEdit);
804 int currentRow = moduleState()->currentPosition();
808 currentRow = queueTable->rowCount();
816 if (moduleState()->jobs().count() > currentRow)
817 moduleState()->setCurrentPosition(currentRow);
820 emit jobsUpdated(moduleState()->getJSONJobs());
827 auto job = moduleState()->jobs().at(index);
834 emit jobsUpdated(moduleState()->getJSONJobs());
841 if (nameEdit->text().isEmpty())
843 process()->appendLogText(
i18n(
"Warning: Target name is required."));
847 if (sequenceEdit->text().isEmpty())
849 process()->appendLogText(
i18n(
"Warning: Sequence file is required."));
854 if ((raBox->isEmpty() || decBox->isEmpty()) && fitsURL.isEmpty())
856 process()->appendLogText(
i18n(
"Warning: Target coordinates are required."));
860 bool raOk =
false, decOk =
false;
861 dms ra(raBox->createDms(&raOk));
862 dms dec(decBox->createDms(&decOk));
866 process()->appendLogText(
i18n(
"Warning: RA value %1 is invalid.", raBox->text()));
872 process()->appendLogText(
i18n(
"Warning: DEC value %1 is invalid.", decBox->text()));
882 if (asapConditionR->isChecked())
883 startCondition = START_ASAP;
886 if (schedulerCompleteSequences->isChecked())
887 stopCondition = FINISH_SEQUENCE;
888 else if (schedulerRepeatSequences->isChecked())
889 stopCondition = FINISH_REPEAT;
890 else if (schedulerUntilTerminated->isChecked())
891 stopCondition = FINISH_LOOP;
893 double altConstraint = SchedulerJob::UNDEFINED_ALTITUDE;
894 if (schedulerAltitude->isChecked())
895 altConstraint = schedulerAltitudeValue->value();
897 double moonSeparation = -1;
898 if (schedulerMoonSeparation->isChecked())
899 moonSeparation = schedulerMoonSeparationValue->value();
901 double moonMaxAltitude = 90;
902 if (schedulerMoonAltitude->isChecked())
903 moonMaxAltitude = schedulerMoonAltitudeMaxValue->value();
905 QString train = opticalTrainCombo->currentText() ==
"--" ?
"" : opticalTrainCombo->currentText();
909 SchedulerUtils::setupJob(*job, nameEdit->text(), leadFollowerSelectionCB->currentIndex() == INDEX_LEAD, groupEdit->text(),
911 KStarsData::Instance()->ut().djd(),
912 positionAngleSpin->value(), sequenceURL, fitsURL,
914 startCondition, startupTimeEdit->dateTime(),
915 stopCondition, schedulerUntilValue->dateTime(), schedulerExecutionSequencesLimit->value(),
920 schedulerWeather->isChecked(),
921 schedulerTwilight->isChecked(),
922 schedulerHorizon->isChecked(),
924 schedulerTrackStep->isChecked(),
925 schedulerFocusStep->isChecked(),
926 schedulerAlignStep->isChecked(),
927 schedulerGuideStep->isChecked());
939 int currentRow = moduleState()->currentPosition() + 1;
944 if (0 <= jobUnderEdit)
947 if (jobUnderEdit != currentRow - 1)
949 qCWarning(KSTARS_EKOS_SCHEDULER) <<
"BUG: the observation job under edit does not match the selected row in the job table.";
953 job = moduleState()->jobs().at(jobUnderEdit);
966 job =
new SchedulerJob();
976 moduleState()->mutlableJobs().insert(currentRow, job);
982 job->setLeadJob(moduleState()->findLead(currentRow - 1));
983 moduleState()->refreshFollowerLists();
991 foreach (SchedulerJob *a_job, moduleState()->jobs())
993 if (a_job == job || !a_job->isLead())
997 else if (a_job->getName() == job->getName())
999 int const a_job_row = moduleState()->jobs().indexOf(a_job);
1002 process()->appendLogText(
i18n(
"Warning: job '%1' at row %2 has a duplicate target at row %3, "
1003 "the scheduler may consider the same storage for captures.",
1004 job->getName(), currentRow, a_job_row));
1007 if (a_job->getSequenceFile() == job->getSequenceFile())
1009 if (a_job->getRepeatsRequired() == job->getRepeatsRequired() && Options::rememberJobProgress())
1010 process()->appendLogText(
i18n(
"Warning: jobs '%1' at row %2 and %3 probably require a different repeat count "
1011 "as currently they will complete simultaneously after %4 batches (or disable option 'Remember job progress')",
1012 job->getName(), currentRow, a_job_row, job->getRepeatsRequired()));
1016 if (++numWarnings >= 1)
1018 process()->appendLogText(
i18n(
"Skipped checking for duplicates."));
1028 queueSaveAsB->setEnabled(
true);
1029 queueSaveB->setEnabled(
true);
1030 startB->setEnabled(
true);
1031 evaluateOnlyB->setEnabled(
true);
1033 checkJobInputComplete();
1035 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 was saved.").
arg(job->getName()).
arg(currentRow + 1);
1039 if (SCHEDULER_LOADING != moduleState()->schedulerState())
1041 process()->evaluateJobs(
true);
1047 nameEdit->setText(job->getName());
1048 groupEdit->setText(job->getGroup());
1050 raBox->show(job->getTargetCoords().
ra0());
1051 decBox->show(job->getTargetCoords().
dec0());
1054 fitsURL = job->getFITSFile().
isEmpty() ?
QUrl() : job->getFITSFile();
1055 fitsEdit->setText(fitsURL.toLocalFile());
1057 schedulerTrackStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_TRACK);
1058 schedulerFocusStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_FOCUS);
1059 schedulerAlignStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_ALIGN);
1060 schedulerGuideStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_GUIDE);
1062 switch (job->getFileStartupCondition())
1065 asapConditionR->setChecked(
true);
1069 startupTimeConditionR->setChecked(
true);
1070 startupTimeEdit->setDateTime(job->getStartupTime());
1074 if (job->getMinAltitude())
1076 schedulerAltitude->setChecked(
true);
1077 schedulerAltitudeValue->setValue(job->getMinAltitude());
1081 schedulerAltitude->setChecked(
false);
1082 schedulerAltitudeValue->setValue(DEFAULT_MIN_ALTITUDE);
1085 if (job->getMinMoonSeparation() > 0)
1087 schedulerMoonSeparation->setChecked(
true);
1088 schedulerMoonSeparationValue->setValue(job->getMinMoonSeparation());
1092 schedulerMoonSeparation->setChecked(
false);
1093 schedulerMoonSeparationValue->setValue(DEFAULT_MIN_MOON_SEPARATION);
1096 if (job->getMaxMoonAltitude() < 90)
1098 schedulerMoonAltitude->setChecked(
true);
1099 schedulerMoonAltitudeMaxValue->setValue(job->getMaxMoonAltitude());
1103 schedulerMoonAltitude->setChecked(
false);
1106 schedulerWeather->setChecked(job->getEnforceWeather());
1108 schedulerTwilight->blockSignals(
true);
1109 schedulerTwilight->setChecked(job->getEnforceTwilight());
1110 schedulerTwilight->blockSignals(
false);
1112 schedulerHorizon->blockSignals(
true);
1113 schedulerHorizon->setChecked(job->getEnforceArtificialHorizon());
1114 schedulerHorizon->blockSignals(
false);
1118 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1122 leadFollowerSelectionCB->setCurrentIndex(INDEX_FOLLOWER);
1125 if (job->getOpticalTrain().
isEmpty())
1126 opticalTrainCombo->setCurrentIndex(0);
1128 opticalTrainCombo->setCurrentText(job->getOpticalTrain());
1130 sequenceURL = job->getSequenceFile();
1131 sequenceEdit->setText(sequenceURL.toLocalFile());
1133 positionAngleSpin->setValue(job->getPositionAngle());
1135 switch (job->getCompletionCondition())
1137 case FINISH_SEQUENCE:
1138 schedulerCompleteSequences->setChecked(
true);
1142 schedulerRepeatSequences->setChecked(
true);
1143 schedulerExecutionSequencesLimit->setValue(job->getRepeatsRequired());
1147 schedulerUntilTerminated->setChecked(
true);
1151 schedulerUntil->setChecked(
true);
1152 schedulerUntilValue->setDateTime(job->getFinishAtTime());
1162 schedulerParkDome->setChecked(Options::schedulerParkDome());
1163 schedulerParkMount->setChecked(Options::schedulerParkMount());
1164 schedulerCloseDustCover->setChecked(Options::schedulerCloseDustCover());
1165 schedulerWarmCCD->setChecked(Options::schedulerWarmCCD());
1166 schedulerUnparkDome->setChecked(Options::schedulerUnparkDome());
1167 schedulerUnparkMount->setChecked(Options::schedulerUnparkMount());
1168 schedulerOpenDustCover->setChecked(Options::schedulerOpenDustCover());
1170 errorHandlingStrategyDelay->setValue(Options::errorHandlingStrategyDelay());
1171 errorHandlingRescheduleErrorsCB->setChecked(Options::rescheduleErrors());
1173 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toString(
QUrl::PreferLocalFile));
1175 if (process()->captureInterface() !=
nullptr)
1177 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
1178 if (hasCoolerControl.
isValid())
1180 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
1181 moduleState()->setCaptureReady(
true);
1189 if (job ==
nullptr && moduleState()->jobs().
size() > 0)
1191 int const currentRow = moduleState()->currentPosition();
1192 if (0 <= currentRow && currentRow < moduleState()->jobs().
size())
1193 job = moduleState()->jobs().at(currentRow);
1197 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Cannot update night time, no matching job found at line" << currentRow;
1202 QDateTime const dawn = job ? job->getDawnAstronomicalTwilight() : moduleState()->Dawn();
1203 QDateTime const dusk = job ? job->getDuskAstronomicalTwilight() : moduleState()->Dusk();
1205 QChar const warning(dawn == dusk ? 0x26A0 :
'-');
1209bool Scheduler::modifyJob(
int index)
1217 queueTable->selectRow(index);
1218 auto modelIndex = queueTable->model()->index(index, 0);
1225 if (jobUnderEdit == i.
row())
1228 SchedulerJob *
const job = moduleState()->jobs().at(i.
row());
1243 startB->setEnabled(
false);
1244 evaluateOnlyB->setEnabled(
false);
1249 jobUnderEdit = i.
row();
1250 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is currently edited.").
arg(job->getName()).
arg(
1260 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.fileName());
1265 Q_UNUSED(deselected)
1268 if (jobChangesAreWatched ==
false || selected.
empty())
1274 if ((current.
row() + 1) > moduleState()->jobs().
size())
1276 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Unexpected row number" << current.
row() <<
"- ignoring.";
1279 moduleState()->setCurrentPosition(current.
row());
1280 SchedulerJob *
const job = moduleState()->jobs().at(current.
row());
1284 if (jobUnderEdit < 0)
1286 else if (jobUnderEdit != current.
row())
1289 process()->appendLogText(
i18n(
"Stop editing of job #%1, resetting to original value.", jobUnderEdit + 1));
1294 else nightTime->setText(
"-");
1303 handleAltitudeGraph(index.
row());
1307 if (index.
isValid() && index.
row() < moduleState()->jobs().count())
1318 addToQueueB->setToolTip(
i18n(
"Use edition fields to create a new job in the observation list."));
1324 addToQueueB->setToolTip(
i18n(
"Apply job changes."));
1327 checkJobInputComplete();
1334 int const currentRow = moduleState()->currentPosition();
1335 if (currentRow >= 0)
1337 SchedulerJob *currentJob = moduleState()->jobs().at(currentRow);
1339 queueUpB->setEnabled(0 < currentRow &&
1340 (currentJob->isLead() || (currentRow > 1 && moduleState()->findLead(currentRow - 2) !=
nullptr)));
1342 queueDownB->setEnabled(currentRow < queueTable->rowCount() - 1 &&
1343 (moduleState()->findLead(currentRow + 1,
false) !=
nullptr));
1348 queueUpB->setEnabled(
false);
1349 queueDownB->setEnabled(
false);
1351 sortJobsB->setEnabled(can_reorder);
1352 removeFromQueueB->setEnabled(can_delete);
1354 nameEdit->setEnabled(is_lead);
1355 selectObjectB->setEnabled(is_lead);
1356 targetStarLabel->setVisible(is_lead);
1357 raBox->setEnabled(is_lead);
1358 decBox->setEnabled(is_lead);
1359 copySkyCenterB->setEnabled(is_lead);
1360 schedulerProfileCombo->setEnabled(is_lead);
1361 fitsEdit->setEnabled(is_lead);
1362 selectFITSB->setEnabled(is_lead);
1363 groupEdit->setEnabled(is_lead);
1364 schedulerTrackStep->setEnabled(is_lead);
1365 schedulerFocusStep->setEnabled(is_lead);
1366 schedulerAlignStep->setEnabled(is_lead);
1367 schedulerGuideStep->setEnabled(is_lead);
1368 startupGroup->setEnabled(is_lead);
1369 contraintsGroup->setEnabled(is_lead);
1372 leadFollowerSelectionCB->setEnabled(moduleState()->findLead(queueTable->currentRow()) !=
nullptr);
1373 if (leadFollowerSelectionCB->isEnabled() ==
false)
1374 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1380 foreach (SchedulerJob* job, moduleState()->jobs())
1381 if (!reordered_sublist.
contains(job))
1382 reordered_sublist.
append(job);
1384 if (moduleState()->jobs() != reordered_sublist)
1387 int const selectedRow = moduleState()->currentPosition();
1388 SchedulerJob *
const selectedJob = 0 <= selectedRow ? moduleState()->jobs().at(selectedRow) :
nullptr;
1391 moduleState()->setJobs(reordered_sublist);
1394 for (SchedulerJob *job : moduleState()->jobs())
1398 if (
nullptr != selectedJob)
1399 moduleState()->setCurrentPosition(moduleState()->jobs().indexOf(selectedJob));
1408 int const rowCount = queueTable->rowCount();
1409 int const currentRow = queueTable->currentRow();
1411 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1413 if (moduleState()->jobs().at(currentRow)->isLead())
1415 int const rows = 1 + job->followerJobs().
count();
1417 if (currentRow - rows < 0)
1421 destinationRow = currentRow - 1 - moduleState()->jobs().at(currentRow - rows)->followerJobs().count();
1424 destinationRow = currentRow - 1;
1427 if (currentRow < 0 || rowCount <= 1 || destinationRow < 0)
1430 if (moduleState()->jobs().at(currentRow)->isLead())
1433 moduleState()->mutlableJobs().removeOne(job);
1434 for (
auto follower : job->followerJobs())
1435 moduleState()->mutlableJobs().removeOne(follower);
1438 moduleState()->mutlableJobs().insert(destinationRow++, job);
1440 for (
auto follower : job->followerJobs())
1441 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1443 for (
int i = currentRow; i > destinationRow; i--)
1446 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1451#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1452 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1454 moduleState()->jobs().swap(currentRow, destinationRow);
1462 moduleState()->setCurrentPosition(destinationRow);
1464 SchedulerJob *newLead = moduleState()->findLead(destinationRow,
true);
1465 if (newLead !=
nullptr)
1467 job->setLeadJob(newLead);
1468 moduleState()->refreshFollowerLists();
1472 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1475 moduleState()->setDirty(
true);
1476 process()->evaluateJobs(
true);
1481 int const rowCount = queueTable->rowCount();
1482 int const currentRow = queueTable->currentRow();
1484 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1486 if (moduleState()->jobs().at(currentRow)->isLead())
1488 int const rows = 1 + job->followerJobs().
count();
1490 if (currentRow + rows >= moduleState()->jobs().count())
1494 destinationRow = currentRow + 1 + moduleState()->jobs().at(currentRow + rows)->followerJobs().count();
1497 destinationRow = currentRow + 1;
1500 if (currentRow < 0 || rowCount <= 1 || destinationRow >= rowCount)
1503 if (moduleState()->jobs().at(currentRow)->isLead())
1506 moduleState()->mutlableJobs().removeOne(job);
1507 for (
auto follower : job->followerJobs())
1508 moduleState()->mutlableJobs().removeOne(follower);
1511 moduleState()->mutlableJobs().insert(destinationRow++, job);
1513 for (
auto follower : job->followerJobs())
1514 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1516 for (
int i = currentRow; i < destinationRow; i++)
1519 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1524#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1525 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1527 moduleState()->mutlableJobs().swap(currentRow, destinationRow);
1533 moduleState()->setCurrentPosition(destinationRow);
1535 if (moduleState()->jobs().at(currentRow)->isLead())
1537 job->setLeadJob(moduleState()->jobs().at(currentRow));
1538 moduleState()->refreshFollowerLists();
1542 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1545 moduleState()->setDirty(
true);
1546 process()->evaluateJobs(
true);
1554 for (
auto onejob : moduleState()->jobs())
1560 const int row = moduleState()->jobs().indexOf(job);
1565 if (row >= queueTable->rowCount())
1568 QTableWidgetItem *nameCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_NAME));
1569 QTableWidgetItem *statusCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STATUS));
1570 QTableWidgetItem *altitudeCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ALTITUDE));
1571 QTableWidgetItem *startupCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STARTTIME));
1572 QTableWidgetItem *completionCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ENDTIME));
1573 QTableWidgetItem *captureCountCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_CAPTURES));
1576 if (!nameCell)
return;
1578 if (
nullptr != nameCell)
1580 nameCell->
setText(job->isLead() ? job->getName() :
"*");
1586 if (
nullptr != statusCell)
1589 static QString stateStringUnknown;
1600 stateStringUnknown =
i18n(
"Unknown");
1602 statusCell->
setText(stateStrings.
value(job->getState(), stateStringUnknown));
1609 if (
nullptr != startupCell)
1611 auto time = (job->getState() ==
SCHEDJOB_BUSY) ? job->getStateTime() : job->getStartupTime();
1616 .arg(job->getAltitudeAtStartup() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1617 .arg(
QChar(job->isSettingAtStartup() ? 0x2193 : 0x2191))
1618 .
arg(job->getAltitudeAtStartup(), 0,
'f', 1)
1619 .
arg(time.toString(startupTimeEdit->displayFormat())));
1620 job->setStartupFormatted(startupCell->
text());
1622 switch (job->getFileStartupCondition())
1651 if (
nullptr != altitudeCell)
1654 bool is_setting =
false;
1655 double const alt = SchedulerUtils::findAltitude(job->getTargetCoords(),
QDateTime(), &is_setting);
1658 .arg(
QChar(is_setting ? 0x2193 : 0x2191))
1659 .arg(alt, 0,
'f', 1));
1661 job->setAltitudeFormatted(altitudeCell->
text());
1667 if (
nullptr != completionCell)
1670 if (job->getStopTime().
isValid())
1673 .arg(job->getAltitudeAtStop() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1674 .arg(
QChar(job->isSettingAtStop() ? 0x2193 : 0x2191))
1675 .
arg(job->getAltitudeAtStop(), 0,
'f', 1)
1676 .
arg(job->getStopTime().
toString(startupTimeEdit->displayFormat())));
1677 job->setEndFormatted(completionCell->
text());
1679 switch (job->getCompletionCondition())
1685 case FINISH_SEQUENCE:
1704 if (
nullptr != captureCountCell)
1706 switch (job->getCompletionCondition())
1713 captureCountCell->
setText(
QString(
"%L1/-").arg(job->getCompletedCount()));
1716 case FINISH_SEQUENCE:
1720 captureCountCell->
setText(
QString(
"%L1/%L2").arg(job->getCompletedCount()).
arg(job->getSequenceCount()));
1724 QString tooltip = job->getProgressSummary();
1725 if (tooltip.
size() == 0)
1726 tooltip =
i18n(
"Count of captures stored for the job, based on its sequence job.\n"
1727 "This is a summary, additional specific frame types may be required to complete the job.");
1735 m_JobUpdateDebounce.start();
1740 const int pos = above ? row : row + 1;
1743 if (row > queueTable->rowCount())
1746 queueTable->insertRow(
pos);
1749 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_NAME), nameCell);
1754 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STATUS), statusCell);
1759 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_CAPTURES), captureCount);
1764 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STARTTIME), startupCell);
1769 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ALTITUDE), altitudeCell);
1774 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ENDTIME), completionCell);
1787void Scheduler::resetJobEdit()
1789 if (jobUnderEdit < 0)
1792 SchedulerJob *
const job = moduleState()->jobs().at(jobUnderEdit);
1793 Q_ASSERT_X(job !=
nullptr, __FUNCTION__,
"Edited job must be valid");
1795 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is not longer edited.").
arg(job->getName()).
arg(
1805 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1808 evaluateOnlyB->setEnabled(
true);
1809 startB->setEnabled(
true);
1812 Q_ASSERT_X(jobUnderEdit == -1, __FUNCTION__,
"No more edited/selected job after exiting edit mode");
1817 int currentRow = moduleState()->currentPosition();
1820 if (moduleState()->
removeJob(currentRow) ==
false)
1828 queueTable->removeRow(currentRow);
1831 if (queueTable->rowCount() == 0)
1833 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1834 evaluateOnlyB->setEnabled(
false);
1835 queueSaveAsB->setEnabled(
false);
1836 queueSaveB->setEnabled(
false);
1837 startB->setEnabled(
false);
1838 pauseB->setEnabled(
false);
1845 queueTable->clearSelection();
1848 if (jobUnderEdit >= 0)
1852 moduleState()->refreshFollowerLists();
1853 process()->evaluateJobs(
true);
1856 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1861 moduleState()->setCurrentPosition(index);
1864void Scheduler::toggleScheduler()
1866 if (moduleState()->schedulerState() == SCHEDULER_RUNNING)
1868 moduleState()->disablePreemptiveShutdown();
1875void Scheduler::pause()
1877 moduleState()->setSchedulerState(SCHEDULER_PAUSED);
1878 process()->appendLogText(
i18n(
"Scheduler pause planned..."));
1879 pauseB->setEnabled(
false);
1882 startB->setToolTip(
i18n(
"Resume Scheduler"));
1885void Scheduler::syncGreedyParams()
1887 process()->getGreedyScheduler()->setParams(
1888 errorHandlingRestartImmediatelyButton->isChecked(),
1889 errorHandlingRestartQueueButton->isChecked(),
1890 errorHandlingRescheduleErrorsCB->isChecked(),
1891 errorHandlingStrategyDelay->value(),
1892 errorHandlingStrategyDelay->value());
1895void Scheduler::handleShutdownStarted()
1897 KSNotification::event(QLatin1String(
"ObservatoryShutdown"),
i18n(
"Observatory is in the shutdown process"),
1898 KSNotification::Scheduler);
1899 weatherLabel->hide();
1902void Ekos::Scheduler::changeSleepLabel(QString text,
bool show)
1904 sleepLabel->setToolTip(text);
1913 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_NOTHING).toLatin1().data());
1916 bool wasAborted =
false;
1917 for (
auto &oneJob : moduleState()->jobs())
1927 KSNotification::event(
QLatin1String(
"SchedulerAborted"),
i18n(
"Scheduler aborted."), KSNotification::Scheduler,
1928 KSNotification::Alert);
1930 startupB->setEnabled(
true);
1931 shutdownB->setEnabled(
true);
1934 if (moduleState()->preemptiveShutdown())
1936 changeSleepLabel(
i18n(
"Scheduler is in shutdown until next job is ready"));
1937 pi->stopAnimation();
1941 changeSleepLabel(
"",
false);
1944 startB->setToolTip(
i18n(
"Start Scheduler"));
1945 pauseB->setEnabled(
false);
1948 queueLoadB->setEnabled(
true);
1949 queueAppendB->setEnabled(
true);
1950 addToQueueB->setEnabled(
true);
1951 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1953 evaluateOnlyB->setEnabled(
true);
1959 return load(
true, path.toLocalFile());
1969 "Ekos Scheduler List (*.esl)");
1976 if (fileURL.
isValid() ==
false)
1979 KSNotification::sorry(message,
i18n(
"Invalid URL"));
1986 process()->removeAllJobs();
1988 const int row = moduleState()->jobs().count();
1993 const bool success = process()->appendEkosScheduleList(fileURL.
toLocalFile());
2000 if (moduleState()->jobs().count() > row)
2001 moduleState()->setCurrentPosition(row);
2004 process()->startJobEvaluation();
2014 if (jobUnderEdit >= 0)
2017 while (queueTable->rowCount() > 0)
2018 queueTable->removeRow(0);
2023 process()->clearLog();
2026void Scheduler::saveAs()
2028 schedulerURL.
clear();
2034 QUrl backupCurrent = schedulerURL;
2035 schedulerURL = path;
2041 schedulerURL = backupCurrent;
2046bool Scheduler::save()
2048 QUrl backupCurrent = schedulerURL;
2051 schedulerURL.
clear();
2054 if (moduleState()->dirty() ==
false && !schedulerURL.
isEmpty())
2061 "Ekos Scheduler List (*.esl)");
2065 schedulerURL = backupCurrent;
2075 if (schedulerURL.isValid())
2077 if ((process()->saveScheduler(schedulerURL)) ==
false)
2079 KSNotification::error(
i18n(
"Failed to save scheduler list"),
i18n(
"Save"));
2084 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.fileName());
2088 QString message =
i18n(
"Invalid URL: %1", schedulerURL.url());
2089 KSNotification::sorry(message,
i18n(
"Invalid URL"));
2096void Scheduler::checkJobInputComplete()
2099 bool const nameSelectionOK = !raBox->isEmpty() && !decBox->isEmpty() && !nameEdit->text().isEmpty();
2102 bool const fitsSelectionOK = !nameEdit->text().isEmpty() && !fitsURL.isEmpty();
2105 bool const seqSelectionOK = !sequenceEdit->text().isEmpty();
2108 bool const addingOK = (nameSelectionOK || fitsSelectionOK) && seqSelectionOK;
2110 addToQueueB->setEnabled(addingOK);
2116 checkJobInputComplete();
2119 if (jobUnderEdit < 0)
2122 moduleState()->setDirty(
true);
2124 if (
sender() == startupProcedureButtonGroup ||
sender() == shutdownProcedureGroup)
2128 if (
sender() == schedulerStartupScript)
2130 else if (
sender() == schedulerShutdownScript)
2131 moduleState()->setShutdownScriptURL(
QUrl::fromUserInput(schedulerShutdownScript->text()));
2137 if (moduleState()->jobs().isEmpty())
2146 using namespace std::placeholders;
2148 std::stable_sort(sortedJobs.
begin() + 1, sortedJobs.
end(),
2149 std::bind(SchedulerJob::decreasingAltitudeOrder, _1, _2, moduleState()->jobs().first()->getStartupTime()));
2154 for (SchedulerJob * job : moduleState()->jobs())
2157 process()->evaluateJobs(
true);
2164 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_SCHEDULER).toLatin1().data());
2165 moduleState()->setupNextIteration(RUN_SCHEDULER);
2171 if (errorHandlingRestartQueueButton->isChecked())
2172 return ERROR_RESTART_AFTER_TERMINATION;
2173 else if (errorHandlingRestartImmediatelyButton->isChecked())
2174 return ERROR_RESTART_IMMEDIATELY;
2176 return ERROR_DONT_RESTART;
2181 errorHandlingStrategyDelay->setEnabled(strategy != ERROR_DONT_RESTART);
2185 case ERROR_RESTART_AFTER_TERMINATION:
2186 errorHandlingRestartQueueButton->setChecked(
true);
2188 case ERROR_RESTART_IMMEDIATELY:
2189 errorHandlingRestartImmediatelyButton->setChecked(
true);
2192 errorHandlingDontRestartButton->setChecked(
true);
2200void Scheduler::setAlgorithm(
int algIndex)
2202 if (algIndex != ALGORITHM_GREEDY)
2204 process()->appendLogText(
2205 i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
2206 algIndex = ALGORITHM_GREEDY;
2208 Options::setSchedulerAlgorithm(algIndex);
2210 groupLabel->setDisabled(
false);
2211 groupEdit->setDisabled(
false);
2212 queueTable->model()->setHeaderData(START_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next Start"));
2213 queueTable->model()->setHeaderData(END_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next End"));
2222 process()->appendLogText(
2223 i18n(
"Turning off astronomical twilight check may cause the observatory to run during daylight. This can cause irreversible damage to your equipment!"));
2227void Scheduler::updateProfiles()
2229 schedulerProfileCombo->blockSignals(
true);
2230 schedulerProfileCombo->clear();
2231 schedulerProfileCombo->addItems(moduleState()->profiles());
2232 schedulerProfileCombo->setCurrentText(moduleState()->currentProfile());
2233 schedulerProfileCombo->blockSignals(
false);
2236void Scheduler::updateJobStageUI(SchedulerJobStage stage)
2241 static QString stageStringUnknown;
2244 stageStrings[SCHEDSTAGE_IDLE] =
i18n(
"Idle");
2245 stageStrings[SCHEDSTAGE_SLEWING] =
i18n(
"Slewing");
2246 stageStrings[SCHEDSTAGE_SLEW_COMPLETE] =
i18n(
"Slew complete");
2247 stageStrings[SCHEDSTAGE_FOCUSING] =
2248 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING] =
i18n(
"Focusing");
2249 stageStrings[SCHEDSTAGE_FOCUS_COMPLETE] =
2250 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING_COMPLETE ] =
i18n(
"Focus complete");
2251 stageStrings[SCHEDSTAGE_ALIGNING] =
i18n(
"Aligning");
2252 stageStrings[SCHEDSTAGE_ALIGN_COMPLETE] =
i18n(
"Align complete");
2253 stageStrings[SCHEDSTAGE_RESLEWING] =
i18n(
"Repositioning");
2254 stageStrings[SCHEDSTAGE_RESLEWING_COMPLETE] =
i18n(
"Repositioning complete");
2256 stageStrings[SCHEDSTAGE_GUIDING] =
i18n(
"Guiding");
2257 stageStrings[SCHEDSTAGE_GUIDING_COMPLETE] =
i18n(
"Guiding complete");
2258 stageStrings[SCHEDSTAGE_CAPTURING] =
i18n(
"Capturing");
2259 stageStringUnknown =
i18n(
"Unknown");
2262 if (activeJob() ==
nullptr)
2263 jobStatus->setText(stageStrings[SCHEDSTAGE_IDLE]);
2265 jobStatus->setText(QString(
"%1: %2").arg(activeJob()->getName(),
2266 stageStrings.
value(stage, stageStringUnknown)));
2272 if (iface == process()->mountInterface())
2274 QVariant canMountPark = process()->mountInterface()->property(
"canPark");
2277 schedulerUnparkMount->setEnabled(canMountPark.
toBool());
2278 schedulerParkMount->setEnabled(canMountPark.
toBool());
2281 else if (iface == process()->capInterface())
2283 QVariant canCapPark = process()->capInterface()->property(
"canPark");
2286 schedulerCloseDustCover->setEnabled(canCapPark.
toBool());
2287 schedulerOpenDustCover->setEnabled(canCapPark.
toBool());
2291 schedulerCloseDustCover->setEnabled(
false);
2292 schedulerOpenDustCover->setEnabled(
false);
2295 else if (iface == process()->weatherInterface())
2297 QVariant status = process()->weatherInterface()->property(
"status");
2298 if (status.isValid())
2303 schedulerWeather->setEnabled(
true);
2306 schedulerWeather->setEnabled(
false);
2308 else if (iface == process()->domeInterface())
2310 QVariant canDomePark = process()->domeInterface()->property(
"canPark");
2313 schedulerUnparkDome->setEnabled(canDomePark.
toBool());
2314 schedulerParkDome->setEnabled(canDomePark.
toBool());
2317 else if (iface == process()->captureInterface())
2319 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
2320 if (hasCoolerControl.
isValid())
2322 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
2327void Scheduler::setWeatherStatus(ISD::Weather::Status status)
2329 TEST_PRINT(stderr,
"sch%d @@@setWeatherStatus(%d)\n", __LINE__,
static_cast<int>(status));
2330 ISD::Weather::Status newStatus = status;
2335 case ISD::Weather::WEATHER_OK:
2336 statusString =
i18n(
"Weather conditions are OK.");
2339 case ISD::Weather::WEATHER_WARNING:
2340 statusString =
i18n(
"Warning: weather conditions are in the WARNING zone.");
2343 case ISD::Weather::WEATHER_ALERT:
2344 statusString =
i18n(
"Caution: weather conditions are in the DANGER zone!");
2351 qCDebug(KSTARS_EKOS_SCHEDULER) << statusString;
2353 if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_OK)
2354 weatherLabel->setPixmap(
2356 .pixmap(
QSize(32, 32)));
2357 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_WARNING)
2359 weatherLabel->setPixmap(
2361 .pixmap(
QSize(32, 32)));
2362 KSNotification::event(
QLatin1String(
"WeatherWarning"),
i18n(
"Weather conditions in warning zone"),
2363 KSNotification::Scheduler, KSNotification::Warn);
2365 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_ALERT)
2367 weatherLabel->setPixmap(
2369 .pixmap(QSize(32, 32)));
2370 KSNotification::event(QLatin1String(
"WeatherAlert"),
2371 i18n(
"Weather conditions are critical. Observatory shutdown is imminent"), KSNotification::Scheduler,
2372 KSNotification::Alert);
2376 .pixmap(QSize(32, 32)));
2378 weatherLabel->show();
2379 weatherLabel->setToolTip(statusString);
2381 process()->appendLogText(statusString);
2383 emit weatherChanged(moduleState()->weatherStatus());
2390 schedulerWeather->setEnabled(
false);
2391 weatherLabel->hide();
2394 changeSleepLabel(
i18n(
"Scheduler is in sleep mode"));
2401 case SCHEDULER_RUNNING:
2403 pi->startAnimation();
2406 startB->setToolTip(
i18n(
"Stop Scheduler"));
2407 pauseB->setEnabled(
true);
2408 pauseB->setChecked(
false);
2411 queueLoadB->setEnabled(
false);
2412 setJobManipulation(
true,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
2414 evaluateOnlyB->setEnabled(
false);
2415 startupB->setEnabled(
false);
2416 shutdownB->setEnabled(
false);
2423 emit newStatus(newState);
2428 pauseB->setCheckable(
true);
2429 pauseB->setChecked(
true);
2432void Scheduler::handleJobsUpdated(
QJsonArray jobsList)
2437 emit jobsUpdated(jobsList);
2443 return assistant->importMosaic(payload);
2446void Scheduler::startupStateChanged(StartupState state)
2448 jobStatus->setText(startupStateString(state));
2450 switch (moduleState()->startupState())
2455 case STARTUP_COMPLETE:
2457 process()->appendLogText(
i18n(
"Manual startup procedure completed successfully."));
2461 process()->appendLogText(
i18n(
"Manual startup procedure terminated due to errors."));
2469void Scheduler::shutdownStateChanged(ShutdownState state)
2471 if (state == SHUTDOWN_COMPLETE || state == SHUTDOWN_IDLE
2472 || state == SHUTDOWN_ERROR)
2475 pi->stopAnimation();
2480 if (state == SHUTDOWN_IDLE)
2481 jobStatus->setText(
i18n(
"Idle"));
2483 jobStatus->setText(shutdownStateString(state));
2485void Scheduler::ekosStateChanged(EkosState state)
2487 if (state == EKOS_IDLE)
2489 jobStatus->setText(
i18n(
"Idle"));
2490 pi->stopAnimation();
2493 jobStatus->setText(ekosStateString(state));
2495void Scheduler::indiStateChanged(INDIState state)
2497 if (state == INDI_IDLE)
2499 jobStatus->setText(
i18n(
"Idle"));
2500 pi->stopAnimation();
2503 jobStatus->setText(indiStateString(state));
2505 refreshOpticalTrain();
2508void Scheduler::indiCommunicationStatusChanged(CommunicationStatus status)
2510 if (status == Success)
2511 refreshOpticalTrain();
2513void Scheduler::parkWaitStateChanged(ParkWaitState state)
2515 jobStatus->setText(parkWaitStateString(state));
2518SchedulerJob *Scheduler::activeJob()
2520 return moduleState()->activeJob();
2523void Scheduler::loadGlobalSettings()
2528 QVariantMap settings;
2532 key = oneWidget->objectName();
2533 value = Options::self()->property(key.
toLatin1());
2534 if (value.
isValid() && oneWidget->count() > 0)
2536 oneWidget->setCurrentText(value.
toString());
2537 settings[key] = value;
2540 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2546 key = oneWidget->objectName();
2547 value = Options::self()->property(key.
toLatin1());
2550 oneWidget->setValue(value.
toDouble());
2551 settings[key] = value;
2554 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2560 key = oneWidget->objectName();
2561 value = Options::self()->property(key.
toLatin1());
2564 oneWidget->setValue(value.
toInt());
2565 settings[key] = value;
2568 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2574 key = oneWidget->objectName();
2575 value = Options::self()->property(key.
toLatin1());
2578 oneWidget->setChecked(value.
toBool());
2579 settings[key] = value;
2582 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2588 key = oneWidget->objectName();
2589 value = Options::self()->property(key.
toLatin1());
2592 oneWidget->setText(value.
toString());
2593 settings[key] = value;
2595 if (key ==
"sequenceEdit")
2597 else if (key ==
"schedulerStartupScript")
2599 else if (key ==
"schedulerShutdownScript")
2603 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2609 key = oneWidget->objectName();
2610 value = Options::self()->property(key.
toLatin1());
2613 oneWidget->setChecked(value.
toBool());
2614 settings[key] = value;
2621 key = oneWidget->objectName();
2622 value = Options::self()->property(key.
toLatin1());
2626 settings[key] = value;
2632 m_GlobalSettings = m_Settings = settings;
2635void Scheduler::syncSettings()
2637 QDoubleSpinBox *dsb =
nullptr;
2638 QSpinBox *sb =
nullptr;
2639 QCheckBox *cb =
nullptr;
2640 QRadioButton *rb =
nullptr;
2641 QComboBox *cbox =
nullptr;
2642 QLineEdit *lineedit =
nullptr;
2643 QDateTimeEdit *datetimeedit =
nullptr;
2647 bool removeKey =
false;
2652 value = dsb->
value();
2658 value = sb->
value();
2686 value = lineedit->
text();
2695 Options::self()->setProperty(key.
toLatin1(), value);
2698 m_Settings.remove(key);
2700 m_Settings[key] = value;
2701 m_GlobalSettings[key] = value;
2703 m_DebounceTimer.start();
2711 emit settingsUpdated(getAllSettings());
2712 Options::self()->save();
2718QVariantMap Scheduler::getAllSettings()
const
2720 QVariantMap settings;
2724 settings.insert(oneWidget->objectName(), oneWidget->currentText());
2728 settings.insert(oneWidget->objectName(), oneWidget->value());
2732 settings.insert(oneWidget->objectName(), oneWidget->value());
2736 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2742 if (!oneWidget->objectName().startsWith(
"qt_"))
2743 settings.insert(oneWidget->objectName(), oneWidget->text());
2748 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2753 settings.insert(oneWidget->objectName(), oneWidget->dateTime().toString(
Qt::ISODate));
2762void Scheduler::setAllSettings(
const QVariantMap &settings)
2766 disconnectSettings();
2768 for (
auto &name : settings.keys())
2774 syncControl(settings, name, comboBox);
2782 syncControl(settings, name, doubleSpinBox);
2790 syncControl(settings, name, spinBox);
2798 syncControl(settings, name, checkbox);
2806 syncControl(settings, name, lineedit);
2808 if (name ==
"sequenceEdit")
2810 else if (name ==
"fitsEdit")
2812 else if (name ==
"schedulerStartupScript")
2814 else if (name ==
"schedulerShutdownScript")
2824 syncControl(settings, name, radioButton);
2831 syncControl(settings, name, datetimeedit);
2836 m_Settings = settings;
2845bool Scheduler::syncControl(
const QVariantMap &settings,
const QString &key, QWidget * widget)
2847 QSpinBox *pSB =
nullptr;
2848 QDoubleSpinBox *pDSB =
nullptr;
2849 QCheckBox *pCB =
nullptr;
2850 QComboBox *pComboBox =
nullptr;
2851 QLineEdit *pLineEdit =
nullptr;
2852 QRadioButton *pRadioButton =
nullptr;
2853 QDateTimeEdit *pDateTimeEdit =
nullptr;
2858 const int value = settings[key].toInt(&ok);
2867 const double value = settings[key].toDouble(&ok);
2876 const bool value = settings[key].toBool();
2884 const QString value = settings[key].toString();
2890 const auto value = settings[key].toString();
2896 const bool value = settings[key].toBool();
2911void Scheduler::refreshOpticalTrain()
2913 opticalTrainCombo->blockSignals(
true);
2914 opticalTrainCombo->clear();
2915 opticalTrainCombo->addItem(
"--");
2916 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
2917 opticalTrainCombo->blockSignals(
false);
2920void Scheduler::connectSettings()
2946 if (!oneWidget->objectName().startsWith(
"qt_"))
2955void Scheduler::disconnectSettings()
2986void Scheduler::handleAltitudeGraph(
int index)
2988 if (!m_altitudeGraph)
2989 m_altitudeGraph =
new SchedulerAltitudeGraph;
2991 if (index < 0 || index >= moduleState()->jobs().
size())
2993 auto job = moduleState()->jobs().at(index);
2995 QDateTime now = SchedulerModuleState::getLocalTime(), start,
end;
2996 QDateTime nextDawn, nextDusk;
2997 SchedulerModuleState::calculateDawnDusk(now, nextDawn, nextDusk);
2999 QVector<double> times, alts;
3000 QDateTime plotStart = (nextDusk < nextDawn) ? nextDusk : nextDusk.addDays(-1);
3009 plotStart = plotStart.
addSecs(-1 * 3600);
3011 auto plotEnd = nextDawn.
addSecs(1 * 3600);
3012 while (t.secsTo(plotEnd) > 0)
3014 double alt = SchedulerUtils::findAltitude(job->getTargetCoords(), t);
3016 double hour = midnight.
secsTo(t) / 3600.0;
3018 t = t.addSecs(60 * 10);
3021 KStarsDateTime ut = SchedulerModuleState::getGeo()->LTtoUT(KStarsDateTime(midnight));
3022 KSAlmanac ksal(ut, SchedulerModuleState::getGeo());
3023 m_altitudeGraph->setTitle(job->getName());
3024 m_altitudeGraph->plot(SchedulerModuleState::getGeo(), &ksal, times, alts);
3027 auto startTime = (job->getState() ==
SCHEDJOB_BUSY) ? job->getStateTime() : job->getStartupTime();
3028 if (startTime.isValid() && startTime < plotEnd && job->getStopTime().
isValid())
3030 auto stopTime = job->getStopTime();
3031 if (startTime < plotStart) startTime = plotStart;
3032 if (stopTime > plotEnd)
3035 QVector<double> runTimes, runAlts;
3037 while (t.secsTo(stopTime) > 0)
3039 double alt = SchedulerUtils::findAltitude(job->getTargetCoords(), t);
3041 double hour = midnight.
secsTo(t) / 3600.0;
3043 t = t.addSecs(60 * 10);
3046 m_altitudeGraph->plot(SchedulerModuleState::getGeo(), &ksal, runTimes, runAlts,
true);
3048 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.
void settleSettings()
settleSettings Run this function after timeout from debounce timer to update database and emit settin...
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 prepareGUI()
prepareGUI Perform once only GUI prep processing
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.
Q_INVOKABLE QAction * action(const QString &name) const
KPageWidgetItem * addPage(QWidget *page, const QString &itemName, const QString &pixmapName=QString(), const QString &header=QString(), bool manage=true)
void setIcon(const QIcon &icon)
static KStars * Instance()
virtual KActionCollection * actionCollection() const
The QProgressIndicator class lets an application display a progress indicator to show that a long tas...
Provides all necessary information about an object in the sky: its coordinates, name(s),...
virtual QString name(void) const
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.
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
bool isValid(QStringView ifopt)
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)
void currentTextChanged(const QString &text)
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)
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