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
495 schedulerExecutionSequencesLimit,
496 errorHandlingStrategyDelay
501 schedulerMoonSeparationValue,
502 schedulerAltitudeValue,
517 for (
auto *
const control : lineEdits)
522 for (
auto *
const control : dateEdits)
527 for (
auto *
const control : comboBoxes)
529 if (control == leadFollowerSelectionCB)
531 this, [
this](
int pos)
533 setJobManipulation(queueUpB->isEnabled() || queueDownB->isEnabled(), removeFromQueueB->isEnabled(),
pos == INDEX_LEAD);
542 for (
auto *
const control : buttonGroups)
543#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
551 for (
auto *
const control : buttons)
556 for (
auto *
const control : spinBoxes)
561 for (
auto *
const control : dspinBoxes)
574 for (
auto *
const control : lineEdits)
576 for (
auto *
const control : dateEdits)
578 for (
auto *
const control : comboBoxes)
580 for (
auto *
const control : buttons)
582 for (
auto *
const control : buttonGroups)
583#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
588 for (
auto *
const control : spinBoxes)
590 for (
auto *
const control : dspinBoxes)
594 jobChangesAreWatched = enable;
599 schedulerRepeatEverything->
setEnabled(Options::rememberJobProgress() ==
false);
600 executionSequenceLimit->setEnabled(Options::rememberJobProgress() ==
false);
605 if (FindDialog::Instance()->execWithParent(Ekos::Manager::Instance()) ==
QDialog::Accepted)
607 SkyObject *
object = FindDialog::Instance()->targetObject();
612void Scheduler::addObject(
SkyObject *
object)
614 if (
object !=
nullptr)
618 if (object->
name() ==
"star")
626 nameEdit->setText(finalObjectName);
627 raBox->show(object->
ra0());
628 decBox->show(object->
dec0());
637 "FITS (*.fits *.fit);;XISF (*.xisf)");
641 processFITSSelection(url);
644void Scheduler::processFITSSelection(
const QUrl &url)
654 const QString filename = fitsEdit->text();
656 double ra = 0, dec = 0;
658 char comment[128], error_status[512];
659 fitsfile *fptr =
nullptr;
661 if (fits_open_diskfile(&fptr, filename.
toLatin1(), READONLY, &status))
663 fits_report_error(stderr, status);
664 fits_get_errstatus(status, error_status);
670 if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status))
672 fits_report_error(stderr, status);
673 fits_get_errstatus(status, error_status);
679 char objectra_str[32] = {0};
680 if (fits_read_key(fptr, TSTRING,
"OBJCTRA", objectra_str, comment, &status))
682 if (fits_read_key(fptr, TDOUBLE,
"RA", &ra, comment, &status))
684 fits_report_error(stderr, status);
685 fits_get_errstatus(status, error_status);
686 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTRA (%1).", QString(error_status)));
698 char objectde_str[32] = {0};
699 if (fits_read_key(fptr, TSTRING,
"OBJCTDEC", objectde_str, comment, &status))
701 if (fits_read_key(fptr, TDOUBLE,
"DEC", &dec, comment, &status))
703 fits_report_error(stderr, status);
704 fits_get_errstatus(status, error_status);
705 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTDEC (%1).", QString(error_status)));
719 char object_str[256] = {0};
720 if (fits_read_key(fptr, TSTRING,
"OBJECT", object_str, comment, &status))
722 QFileInfo info(filename);
723 nameEdit->setText(info.completeBaseName());
727 nameEdit->setText(object_str);
739 sequenceEdit->setText(sequenceURL.toLocalFile());
747 dirPath.toLocalFile(),
748 i18n(
"Ekos Sequence Queue (*.esq)"));
756 "Select Startup Script"),
758 i18n(
"Script (*)")));
759 if (moduleState()->startupScriptURL().isEmpty())
764 moduleState()->setDirty(
true);
765 schedulerStartupScript->setText(moduleState()->startupScriptURL().toLocalFile());
771 "Select Shutdown Script"),
773 i18n(
"Script (*)")));
774 if (moduleState()->shutdownScriptURL().isEmpty())
779 moduleState()->setDirty(
true);
780 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toLocalFile());
785 if (0 <= jobUnderEdit)
788 job = moduleState()->jobs().at(jobUnderEdit);
798 int currentRow = moduleState()->currentPosition();
802 currentRow = queueTable->rowCount();
810 if (moduleState()->jobs().count() > currentRow)
811 moduleState()->setCurrentPosition(currentRow);
814 emit jobsUpdated(moduleState()->getJSONJobs());
821 auto job = moduleState()->jobs().at(index);
828 emit jobsUpdated(moduleState()->getJSONJobs());
835 if (nameEdit->text().isEmpty())
837 process()->appendLogText(
i18n(
"Warning: Target name is required."));
841 if (sequenceEdit->text().isEmpty())
843 process()->appendLogText(
i18n(
"Warning: Sequence file is required."));
848 if ((raBox->isEmpty() || decBox->isEmpty()) && fitsURL.isEmpty())
850 process()->appendLogText(
i18n(
"Warning: Target coordinates are required."));
854 bool raOk =
false, decOk =
false;
855 dms ra(raBox->createDms(&raOk));
856 dms dec(decBox->createDms(&decOk));
860 process()->appendLogText(
i18n(
"Warning: RA value %1 is invalid.", raBox->text()));
866 process()->appendLogText(
i18n(
"Warning: DEC value %1 is invalid.", decBox->text()));
876 if (asapConditionR->isChecked())
877 startCondition = START_ASAP;
880 if (schedulerCompleteSequences->isChecked())
881 stopCondition = FINISH_SEQUENCE;
882 else if (schedulerRepeatSequences->isChecked())
883 stopCondition = FINISH_REPEAT;
884 else if (schedulerUntilTerminated->isChecked())
885 stopCondition = FINISH_LOOP;
887 double altConstraint = SchedulerJob::UNDEFINED_ALTITUDE;
888 if (schedulerAltitude->isChecked())
889 altConstraint = schedulerAltitudeValue->value();
891 double moonConstraint = -1;
892 if (schedulerMoonSeparation->isChecked())
893 moonConstraint = schedulerMoonSeparationValue->value();
895 QString train = opticalTrainCombo->currentText() ==
"--" ?
"" : opticalTrainCombo->currentText();
899 SchedulerUtils::setupJob(*job, nameEdit->text(), leadFollowerSelectionCB->currentIndex() == INDEX_LEAD, groupEdit->text(),
901 KStarsData::Instance()->ut().djd(),
902 positionAngleSpin->value(), sequenceURL, fitsURL,
904 startCondition, startupTimeEdit->dateTime(),
905 stopCondition, schedulerUntilValue->dateTime(), schedulerExecutionSequencesLimit->value(),
909 schedulerWeather->isChecked(),
910 schedulerTwilight->isChecked(),
911 schedulerHorizon->isChecked(),
913 schedulerTrackStep->isChecked(),
914 schedulerFocusStep->isChecked(),
915 schedulerAlignStep->isChecked(),
916 schedulerGuideStep->isChecked());
928 int currentRow = moduleState()->currentPosition() + 1;
933 if (0 <= jobUnderEdit)
936 if (jobUnderEdit != currentRow - 1)
938 qCWarning(KSTARS_EKOS_SCHEDULER) <<
"BUG: the observation job under edit does not match the selected row in the job table.";
942 job = moduleState()->jobs().at(jobUnderEdit);
955 job =
new SchedulerJob();
965 moduleState()->mutlableJobs().insert(currentRow, job);
971 job->setLeadJob(moduleState()->findLead(currentRow - 1));
972 moduleState()->refreshFollowerLists();
980 foreach (SchedulerJob *a_job, moduleState()->jobs())
982 if (a_job == job || !a_job->isLead())
986 else if (a_job->getName() == job->getName())
988 int const a_job_row = moduleState()->jobs().indexOf(a_job);
991 process()->appendLogText(
i18n(
"Warning: job '%1' at row %2 has a duplicate target at row %3, "
992 "the scheduler may consider the same storage for captures.",
993 job->getName(), currentRow, a_job_row));
996 if (a_job->getSequenceFile() == job->getSequenceFile())
998 if (a_job->getRepeatsRequired() == job->getRepeatsRequired() && Options::rememberJobProgress())
999 process()->appendLogText(
i18n(
"Warning: jobs '%1' at row %2 and %3 probably require a different repeat count "
1000 "as currently they will complete simultaneously after %4 batches (or disable option 'Remember job progress')",
1001 job->getName(), currentRow, a_job_row, job->getRepeatsRequired()));
1005 if (++numWarnings >= 1)
1007 process()->appendLogText(
i18n(
"Skipped checking for duplicates."));
1017 queueSaveAsB->setEnabled(
true);
1018 queueSaveB->setEnabled(
true);
1019 startB->setEnabled(
true);
1020 evaluateOnlyB->setEnabled(
true);
1022 checkJobInputComplete();
1024 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 was saved.").
arg(job->getName()).
arg(currentRow + 1);
1028 if (SCHEDULER_LOADING != moduleState()->schedulerState())
1030 process()->evaluateJobs(
true);
1036 nameEdit->setText(job->getName());
1037 groupEdit->setText(job->getGroup());
1039 raBox->show(job->getTargetCoords().
ra0());
1040 decBox->show(job->getTargetCoords().
dec0());
1043 fitsURL = job->getFITSFile().
isEmpty() ?
QUrl() : job->getFITSFile();
1044 fitsEdit->setText(fitsURL.toLocalFile());
1046 schedulerTrackStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_TRACK);
1047 schedulerFocusStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_FOCUS);
1048 schedulerAlignStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_ALIGN);
1049 schedulerGuideStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_GUIDE);
1051 switch (job->getFileStartupCondition())
1054 asapConditionR->setChecked(
true);
1058 startupTimeConditionR->setChecked(
true);
1059 startupTimeEdit->setDateTime(job->getStartupTime());
1063 if (job->getMinAltitude())
1065 schedulerAltitude->setChecked(
true);
1066 schedulerAltitudeValue->setValue(job->getMinAltitude());
1070 schedulerAltitude->setChecked(
false);
1071 schedulerAltitudeValue->setValue(DEFAULT_MIN_ALTITUDE);
1074 if (job->getMinMoonSeparation() >= 0)
1076 schedulerMoonSeparation->setChecked(
true);
1077 schedulerMoonSeparationValue->setValue(job->getMinMoonSeparation());
1081 schedulerMoonSeparation->setChecked(
false);
1082 schedulerMoonSeparationValue->setValue(DEFAULT_MIN_MOON_SEPARATION);
1085 schedulerWeather->setChecked(job->getEnforceWeather());
1087 schedulerTwilight->blockSignals(
true);
1088 schedulerTwilight->setChecked(job->getEnforceTwilight());
1089 schedulerTwilight->blockSignals(
false);
1091 schedulerHorizon->blockSignals(
true);
1092 schedulerHorizon->setChecked(job->getEnforceArtificialHorizon());
1093 schedulerHorizon->blockSignals(
false);
1097 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1101 leadFollowerSelectionCB->setCurrentIndex(INDEX_FOLLOWER);
1104 if (job->getOpticalTrain().
isEmpty())
1105 opticalTrainCombo->setCurrentIndex(0);
1107 opticalTrainCombo->setCurrentText(job->getOpticalTrain());
1109 sequenceURL = job->getSequenceFile();
1110 sequenceEdit->setText(sequenceURL.toLocalFile());
1112 positionAngleSpin->setValue(job->getPositionAngle());
1114 switch (job->getCompletionCondition())
1116 case FINISH_SEQUENCE:
1117 schedulerCompleteSequences->setChecked(
true);
1121 schedulerRepeatSequences->setChecked(
true);
1122 schedulerExecutionSequencesLimit->setValue(job->getRepeatsRequired());
1126 schedulerUntilTerminated->setChecked(
true);
1130 schedulerUntil->setChecked(
true);
1131 schedulerUntilValue->setDateTime(job->getFinishAtTime());
1141 schedulerParkDome->setChecked(Options::schedulerParkDome());
1142 schedulerParkMount->setChecked(Options::schedulerParkMount());
1143 schedulerCloseDustCover->setChecked(Options::schedulerCloseDustCover());
1144 schedulerWarmCCD->setChecked(Options::schedulerWarmCCD());
1145 schedulerUnparkDome->setChecked(Options::schedulerUnparkDome());
1146 schedulerUnparkMount->setChecked(Options::schedulerUnparkMount());
1147 schedulerOpenDustCover->setChecked(Options::schedulerOpenDustCover());
1149 errorHandlingStrategyDelay->setValue(Options::errorHandlingStrategyDelay());
1150 errorHandlingRescheduleErrorsCB->setChecked(Options::rescheduleErrors());
1152 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toString(
QUrl::PreferLocalFile));
1154 if (process()->captureInterface() !=
nullptr)
1156 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
1157 if (hasCoolerControl.
isValid())
1159 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
1160 moduleState()->setCaptureReady(
true);
1168 if (job ==
nullptr && moduleState()->jobs().
size() > 0)
1170 int const currentRow = moduleState()->currentPosition();
1171 if (0 <= currentRow && currentRow < moduleState()->jobs().
size())
1172 job = moduleState()->jobs().at(currentRow);
1176 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Cannot update night time, no matching job found at line" << currentRow;
1181 QDateTime const dawn = job ? job->getDawnAstronomicalTwilight() : moduleState()->Dawn();
1182 QDateTime const dusk = job ? job->getDuskAstronomicalTwilight() : moduleState()->Dusk();
1184 QChar const warning(dawn == dusk ? 0x26A0 :
'-');
1188bool Scheduler::modifyJob(
int index)
1196 queueTable->selectRow(index);
1197 auto modelIndex = queueTable->model()->index(index, 0);
1204 if (jobUnderEdit == i.
row())
1207 SchedulerJob *
const job = moduleState()->jobs().at(i.
row());
1222 startB->setEnabled(
false);
1223 evaluateOnlyB->setEnabled(
false);
1228 jobUnderEdit = i.
row();
1229 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is currently edited.").
arg(job->getName()).
arg(
1239 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.fileName());
1244 Q_UNUSED(deselected)
1247 if (jobChangesAreWatched ==
false || selected.
empty())
1253 if ((current.
row() + 1) > moduleState()->jobs().
size())
1255 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Unexpected row number" << current.
row() <<
"- ignoring.";
1258 moduleState()->setCurrentPosition(current.
row());
1259 SchedulerJob *
const job = moduleState()->jobs().at(current.
row());
1263 if (jobUnderEdit < 0)
1265 else if (jobUnderEdit != current.
row())
1268 process()->appendLogText(
i18n(
"Stop editing of job #%1, resetting to original value.", jobUnderEdit + 1));
1273 else nightTime->setText(
"-");
1282 handleAltitudeGraph(index.
row());
1286 if (index.
isValid() && index.
row() < moduleState()->jobs().count())
1297 addToQueueB->setToolTip(
i18n(
"Use edition fields to create a new job in the observation list."));
1303 addToQueueB->setToolTip(
i18n(
"Apply job changes."));
1306 checkJobInputComplete();
1313 int const currentRow = moduleState()->currentPosition();
1314 if (currentRow >= 0)
1316 SchedulerJob *currentJob = moduleState()->jobs().at(currentRow);
1318 queueUpB->setEnabled(0 < currentRow &&
1319 (currentJob->isLead() || (currentRow > 1 && moduleState()->findLead(currentRow - 2) !=
nullptr)));
1321 queueDownB->setEnabled(currentRow < queueTable->rowCount() - 1 &&
1322 (moduleState()->findLead(currentRow + 1,
false) !=
nullptr));
1327 queueUpB->setEnabled(
false);
1328 queueDownB->setEnabled(
false);
1330 sortJobsB->setEnabled(can_reorder);
1331 removeFromQueueB->setEnabled(can_delete);
1333 nameEdit->setEnabled(is_lead);
1334 selectObjectB->setEnabled(is_lead);
1335 targetStarLabel->setVisible(is_lead);
1336 raBox->setEnabled(is_lead);
1337 decBox->setEnabled(is_lead);
1338 copySkyCenterB->setEnabled(is_lead);
1339 schedulerProfileCombo->setEnabled(is_lead);
1340 fitsEdit->setEnabled(is_lead);
1341 selectFITSB->setEnabled(is_lead);
1342 groupEdit->setEnabled(is_lead);
1343 schedulerTrackStep->setEnabled(is_lead);
1344 schedulerFocusStep->setEnabled(is_lead);
1345 schedulerAlignStep->setEnabled(is_lead);
1346 schedulerGuideStep->setEnabled(is_lead);
1347 startupGroup->setEnabled(is_lead);
1348 contraintsGroup->setEnabled(is_lead);
1351 leadFollowerSelectionCB->setEnabled(moduleState()->findLead(queueTable->currentRow()) !=
nullptr);
1352 if (leadFollowerSelectionCB->isEnabled() ==
false)
1353 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1359 foreach (SchedulerJob* job, moduleState()->jobs())
1360 if (!reordered_sublist.
contains(job))
1361 reordered_sublist.
append(job);
1363 if (moduleState()->jobs() != reordered_sublist)
1366 int const selectedRow = moduleState()->currentPosition();
1367 SchedulerJob *
const selectedJob = 0 <= selectedRow ? moduleState()->jobs().at(selectedRow) :
nullptr;
1370 moduleState()->setJobs(reordered_sublist);
1373 for (SchedulerJob *job : moduleState()->jobs())
1377 if (
nullptr != selectedJob)
1378 moduleState()->setCurrentPosition(moduleState()->jobs().indexOf(selectedJob));
1387 int const rowCount = queueTable->rowCount();
1388 int const currentRow = queueTable->currentRow();
1390 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1392 if (moduleState()->jobs().at(currentRow)->isLead())
1394 int const rows = 1 + job->followerJobs().
count();
1396 if (currentRow - rows < 0)
1400 destinationRow = currentRow - 1 - moduleState()->jobs().at(currentRow - rows)->followerJobs().count();
1403 destinationRow = currentRow - 1;
1406 if (currentRow < 0 || rowCount <= 1 || destinationRow < 0)
1409 if (moduleState()->jobs().at(currentRow)->isLead())
1412 moduleState()->mutlableJobs().removeOne(job);
1413 for (
auto follower : job->followerJobs())
1414 moduleState()->mutlableJobs().removeOne(follower);
1417 moduleState()->mutlableJobs().insert(destinationRow++, job);
1419 for (
auto follower : job->followerJobs())
1420 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1422 for (
int i = currentRow; i > destinationRow; i--)
1425 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1430#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1431 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1433 moduleState()->jobs().swap(currentRow, destinationRow);
1441 moduleState()->setCurrentPosition(destinationRow);
1443 SchedulerJob *newLead = moduleState()->findLead(destinationRow,
true);
1444 if (newLead !=
nullptr)
1446 job->setLeadJob(newLead);
1447 moduleState()->refreshFollowerLists();
1451 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1454 moduleState()->setDirty(
true);
1455 process()->evaluateJobs(
true);
1460 int const rowCount = queueTable->rowCount();
1461 int const currentRow = queueTable->currentRow();
1463 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1465 if (moduleState()->jobs().at(currentRow)->isLead())
1467 int const rows = 1 + job->followerJobs().
count();
1469 if (currentRow + rows >= moduleState()->jobs().count())
1473 destinationRow = currentRow + 1 + moduleState()->jobs().at(currentRow + rows)->followerJobs().count();
1476 destinationRow = currentRow + 1;
1479 if (currentRow < 0 || rowCount <= 1 || destinationRow >= rowCount)
1482 if (moduleState()->jobs().at(currentRow)->isLead())
1485 moduleState()->mutlableJobs().removeOne(job);
1486 for (
auto follower : job->followerJobs())
1487 moduleState()->mutlableJobs().removeOne(follower);
1490 moduleState()->mutlableJobs().insert(destinationRow++, job);
1492 for (
auto follower : job->followerJobs())
1493 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1495 for (
int i = currentRow; i < destinationRow; i++)
1498 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1503#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1504 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1506 moduleState()->mutlableJobs().swap(currentRow, destinationRow);
1512 moduleState()->setCurrentPosition(destinationRow);
1514 if (moduleState()->jobs().at(currentRow)->isLead())
1516 job->setLeadJob(moduleState()->jobs().at(currentRow));
1517 moduleState()->refreshFollowerLists();
1521 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1524 moduleState()->setDirty(
true);
1525 process()->evaluateJobs(
true);
1533 for (
auto onejob : moduleState()->jobs())
1539 const int row = moduleState()->jobs().indexOf(job);
1544 if (row >= queueTable->rowCount())
1547 QTableWidgetItem *nameCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_NAME));
1548 QTableWidgetItem *statusCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STATUS));
1549 QTableWidgetItem *altitudeCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ALTITUDE));
1550 QTableWidgetItem *startupCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STARTTIME));
1551 QTableWidgetItem *completionCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ENDTIME));
1552 QTableWidgetItem *captureCountCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_CAPTURES));
1555 if (!nameCell)
return;
1557 if (
nullptr != nameCell)
1559 nameCell->
setText(job->isLead() ? job->getName() :
"*");
1565 if (
nullptr != statusCell)
1568 static QString stateStringUnknown;
1579 stateStringUnknown =
i18n(
"Unknown");
1581 statusCell->
setText(stateStrings.
value(job->getState(), stateStringUnknown));
1588 if (
nullptr != startupCell)
1590 auto time = (job->getState() ==
SCHEDJOB_BUSY) ? job->getStateTime() : job->getStartupTime();
1595 .arg(job->getAltitudeAtStartup() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1596 .arg(
QChar(job->isSettingAtStartup() ? 0x2193 : 0x2191))
1597 .
arg(job->getAltitudeAtStartup(), 0,
'f', 1)
1598 .
arg(time.toString(startupTimeEdit->displayFormat())));
1599 job->setStartupFormatted(startupCell->
text());
1601 switch (job->getFileStartupCondition())
1630 if (
nullptr != altitudeCell)
1633 bool is_setting =
false;
1634 double const alt = SchedulerUtils::findAltitude(job->getTargetCoords(),
QDateTime(), &is_setting);
1637 .arg(
QChar(is_setting ? 0x2193 : 0x2191))
1638 .arg(alt, 0,
'f', 1));
1640 job->setAltitudeFormatted(altitudeCell->
text());
1646 if (
nullptr != completionCell)
1649 if (job->getStopTime().
isValid())
1652 .arg(job->getAltitudeAtStop() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1653 .arg(
QChar(job->isSettingAtStop() ? 0x2193 : 0x2191))
1654 .
arg(job->getAltitudeAtStop(), 0,
'f', 1)
1655 .
arg(job->getStopTime().
toString(startupTimeEdit->displayFormat())));
1656 job->setEndFormatted(completionCell->
text());
1658 switch (job->getCompletionCondition())
1664 case FINISH_SEQUENCE:
1683 if (
nullptr != captureCountCell)
1685 switch (job->getCompletionCondition())
1692 captureCountCell->
setText(
QString(
"%L1/-").arg(job->getCompletedCount()));
1695 case FINISH_SEQUENCE:
1699 captureCountCell->
setText(
QString(
"%L1/%L2").arg(job->getCompletedCount()).
arg(job->getSequenceCount()));
1703 QString tooltip = job->getProgressSummary();
1704 if (tooltip.
size() == 0)
1705 tooltip =
i18n(
"Count of captures stored for the job, based on its sequence job.\n"
1706 "This is a summary, additional specific frame types may be required to complete the job.");
1714 m_JobUpdateDebounce.start();
1719 const int pos = above ? row : row + 1;
1722 if (row > queueTable->rowCount())
1725 queueTable->insertRow(
pos);
1728 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_NAME), nameCell);
1733 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STATUS), statusCell);
1738 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_CAPTURES), captureCount);
1743 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STARTTIME), startupCell);
1748 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ALTITUDE), altitudeCell);
1753 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ENDTIME), completionCell);
1766void Scheduler::resetJobEdit()
1768 if (jobUnderEdit < 0)
1771 SchedulerJob *
const job = moduleState()->jobs().at(jobUnderEdit);
1772 Q_ASSERT_X(job !=
nullptr, __FUNCTION__,
"Edited job must be valid");
1774 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is not longer edited.").
arg(job->getName()).
arg(
1784 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1787 evaluateOnlyB->setEnabled(
true);
1788 startB->setEnabled(
true);
1791 Q_ASSERT_X(jobUnderEdit == -1, __FUNCTION__,
"No more edited/selected job after exiting edit mode");
1796 int currentRow = moduleState()->currentPosition();
1799 if (moduleState()->
removeJob(currentRow) ==
false)
1807 queueTable->removeRow(currentRow);
1810 if (queueTable->rowCount() == 0)
1812 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1813 evaluateOnlyB->setEnabled(
false);
1814 queueSaveAsB->setEnabled(
false);
1815 queueSaveB->setEnabled(
false);
1816 startB->setEnabled(
false);
1817 pauseB->setEnabled(
false);
1824 queueTable->clearSelection();
1827 if (jobUnderEdit >= 0)
1831 moduleState()->refreshFollowerLists();
1832 process()->evaluateJobs(
true);
1835 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1840 moduleState()->setCurrentPosition(index);
1843void Scheduler::toggleScheduler()
1845 if (moduleState()->schedulerState() == SCHEDULER_RUNNING)
1847 moduleState()->disablePreemptiveShutdown();
1854void Scheduler::pause()
1856 moduleState()->setSchedulerState(SCHEDULER_PAUSED);
1857 process()->appendLogText(
i18n(
"Scheduler pause planned..."));
1858 pauseB->setEnabled(
false);
1861 startB->setToolTip(
i18n(
"Resume Scheduler"));
1864void Scheduler::syncGreedyParams()
1866 process()->getGreedyScheduler()->setParams(
1867 errorHandlingRestartImmediatelyButton->isChecked(),
1868 errorHandlingRestartQueueButton->isChecked(),
1869 errorHandlingRescheduleErrorsCB->isChecked(),
1870 errorHandlingStrategyDelay->value(),
1871 errorHandlingStrategyDelay->value());
1874void Scheduler::handleShutdownStarted()
1876 KSNotification::event(QLatin1String(
"ObservatoryShutdown"),
i18n(
"Observatory is in the shutdown process"),
1877 KSNotification::Scheduler);
1878 weatherLabel->hide();
1881void Ekos::Scheduler::changeSleepLabel(QString text,
bool show)
1883 sleepLabel->setToolTip(text);
1892 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_NOTHING).toLatin1().data());
1895 bool wasAborted =
false;
1896 for (
auto &oneJob : moduleState()->jobs())
1906 KSNotification::event(
QLatin1String(
"SchedulerAborted"),
i18n(
"Scheduler aborted."), KSNotification::Scheduler,
1907 KSNotification::Alert);
1909 startupB->setEnabled(
true);
1910 shutdownB->setEnabled(
true);
1913 if (moduleState()->preemptiveShutdown())
1915 changeSleepLabel(
i18n(
"Scheduler is in shutdown until next job is ready"));
1916 pi->stopAnimation();
1920 changeSleepLabel(
"",
false);
1923 startB->setToolTip(
i18n(
"Start Scheduler"));
1924 pauseB->setEnabled(
false);
1927 queueLoadB->setEnabled(
true);
1928 queueAppendB->setEnabled(
true);
1929 addToQueueB->setEnabled(
true);
1930 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1932 evaluateOnlyB->setEnabled(
true);
1938 return load(
true, path.toLocalFile());
1948 "Ekos Scheduler List (*.esl)");
1955 if (fileURL.
isValid() ==
false)
1958 KSNotification::sorry(message,
i18n(
"Invalid URL"));
1965 process()->removeAllJobs();
1967 const int row = moduleState()->jobs().count();
1972 const bool success = process()->appendEkosScheduleList(fileURL.
toLocalFile());
1979 if (moduleState()->jobs().count() > row)
1980 moduleState()->setCurrentPosition(row);
1983 process()->startJobEvaluation();
1993 if (jobUnderEdit >= 0)
1996 while (queueTable->rowCount() > 0)
1997 queueTable->removeRow(0);
2002 process()->clearLog();
2005void Scheduler::saveAs()
2007 schedulerURL.
clear();
2013 QUrl backupCurrent = schedulerURL;
2014 schedulerURL = path;
2020 schedulerURL = backupCurrent;
2025bool Scheduler::save()
2027 QUrl backupCurrent = schedulerURL;
2030 schedulerURL.
clear();
2033 if (moduleState()->dirty() ==
false && !schedulerURL.
isEmpty())
2040 "Ekos Scheduler List (*.esl)");
2044 schedulerURL = backupCurrent;
2054 if (schedulerURL.isValid())
2056 if ((process()->saveScheduler(schedulerURL)) ==
false)
2058 KSNotification::error(
i18n(
"Failed to save scheduler list"),
i18n(
"Save"));
2063 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.fileName());
2067 QString message =
i18n(
"Invalid URL: %1", schedulerURL.url());
2068 KSNotification::sorry(message,
i18n(
"Invalid URL"));
2075void Scheduler::checkJobInputComplete()
2078 bool const nameSelectionOK = !raBox->isEmpty() && !decBox->isEmpty() && !nameEdit->text().isEmpty();
2081 bool const fitsSelectionOK = !nameEdit->text().isEmpty() && !fitsURL.isEmpty();
2084 bool const seqSelectionOK = !sequenceEdit->text().isEmpty();
2087 bool const addingOK = (nameSelectionOK || fitsSelectionOK) && seqSelectionOK;
2089 addToQueueB->setEnabled(addingOK);
2095 checkJobInputComplete();
2098 if (jobUnderEdit < 0)
2101 moduleState()->setDirty(
true);
2103 if (
sender() == startupProcedureButtonGroup ||
sender() == shutdownProcedureGroup)
2107 if (
sender() == schedulerStartupScript)
2109 else if (
sender() == schedulerShutdownScript)
2110 moduleState()->setShutdownScriptURL(
QUrl::fromUserInput(schedulerShutdownScript->text()));
2116 if (moduleState()->jobs().isEmpty())
2125 using namespace std::placeholders;
2127 std::stable_sort(sortedJobs.
begin() + 1, sortedJobs.
end(),
2128 std::bind(SchedulerJob::decreasingAltitudeOrder, _1, _2, moduleState()->jobs().first()->getStartupTime()));
2133 for (SchedulerJob * job : moduleState()->jobs())
2136 process()->evaluateJobs(
true);
2143 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_SCHEDULER).toLatin1().data());
2144 moduleState()->setupNextIteration(RUN_SCHEDULER);
2150 if (errorHandlingRestartQueueButton->isChecked())
2151 return ERROR_RESTART_AFTER_TERMINATION;
2152 else if (errorHandlingRestartImmediatelyButton->isChecked())
2153 return ERROR_RESTART_IMMEDIATELY;
2155 return ERROR_DONT_RESTART;
2160 errorHandlingStrategyDelay->setEnabled(strategy != ERROR_DONT_RESTART);
2164 case ERROR_RESTART_AFTER_TERMINATION:
2165 errorHandlingRestartQueueButton->setChecked(
true);
2167 case ERROR_RESTART_IMMEDIATELY:
2168 errorHandlingRestartImmediatelyButton->setChecked(
true);
2171 errorHandlingDontRestartButton->setChecked(
true);
2179void Scheduler::setAlgorithm(
int algIndex)
2181 if (algIndex != ALGORITHM_GREEDY)
2183 process()->appendLogText(
2184 i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
2185 algIndex = ALGORITHM_GREEDY;
2187 Options::setSchedulerAlgorithm(algIndex);
2189 groupLabel->setDisabled(
false);
2190 groupEdit->setDisabled(
false);
2191 queueTable->model()->setHeaderData(START_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next Start"));
2192 queueTable->model()->setHeaderData(END_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next End"));
2201 process()->appendLogText(
2202 i18n(
"Turning off astronomical twilight check may cause the observatory to run during daylight. This can cause irreversible damage to your equipment!"));
2206void Scheduler::updateProfiles()
2208 schedulerProfileCombo->blockSignals(
true);
2209 schedulerProfileCombo->clear();
2210 schedulerProfileCombo->addItems(moduleState()->profiles());
2211 schedulerProfileCombo->setCurrentText(moduleState()->currentProfile());
2212 schedulerProfileCombo->blockSignals(
false);
2215void Scheduler::updateJobStageUI(SchedulerJobStage stage)
2220 static QString stageStringUnknown;
2223 stageStrings[SCHEDSTAGE_IDLE] =
i18n(
"Idle");
2224 stageStrings[SCHEDSTAGE_SLEWING] =
i18n(
"Slewing");
2225 stageStrings[SCHEDSTAGE_SLEW_COMPLETE] =
i18n(
"Slew complete");
2226 stageStrings[SCHEDSTAGE_FOCUSING] =
2227 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING] =
i18n(
"Focusing");
2228 stageStrings[SCHEDSTAGE_FOCUS_COMPLETE] =
2229 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING_COMPLETE ] =
i18n(
"Focus complete");
2230 stageStrings[SCHEDSTAGE_ALIGNING] =
i18n(
"Aligning");
2231 stageStrings[SCHEDSTAGE_ALIGN_COMPLETE] =
i18n(
"Align complete");
2232 stageStrings[SCHEDSTAGE_RESLEWING] =
i18n(
"Repositioning");
2233 stageStrings[SCHEDSTAGE_RESLEWING_COMPLETE] =
i18n(
"Repositioning complete");
2235 stageStrings[SCHEDSTAGE_GUIDING] =
i18n(
"Guiding");
2236 stageStrings[SCHEDSTAGE_GUIDING_COMPLETE] =
i18n(
"Guiding complete");
2237 stageStrings[SCHEDSTAGE_CAPTURING] =
i18n(
"Capturing");
2238 stageStringUnknown =
i18n(
"Unknown");
2241 if (activeJob() ==
nullptr)
2242 jobStatus->setText(stageStrings[SCHEDSTAGE_IDLE]);
2244 jobStatus->setText(QString(
"%1: %2").arg(activeJob()->getName(),
2245 stageStrings.
value(stage, stageStringUnknown)));
2251 if (iface == process()->mountInterface())
2253 QVariant canMountPark = process()->mountInterface()->property(
"canPark");
2256 schedulerUnparkMount->setEnabled(canMountPark.
toBool());
2257 schedulerParkMount->setEnabled(canMountPark.
toBool());
2260 else if (iface == process()->capInterface())
2262 QVariant canCapPark = process()->capInterface()->property(
"canPark");
2265 schedulerCloseDustCover->setEnabled(canCapPark.
toBool());
2266 schedulerOpenDustCover->setEnabled(canCapPark.
toBool());
2270 schedulerCloseDustCover->setEnabled(
false);
2271 schedulerOpenDustCover->setEnabled(
false);
2274 else if (iface == process()->weatherInterface())
2276 QVariant status = process()->weatherInterface()->property(
"status");
2277 if (status.isValid())
2282 schedulerWeather->setEnabled(
true);
2285 schedulerWeather->setEnabled(
false);
2287 else if (iface == process()->domeInterface())
2289 QVariant canDomePark = process()->domeInterface()->property(
"canPark");
2292 schedulerUnparkDome->setEnabled(canDomePark.
toBool());
2293 schedulerParkDome->setEnabled(canDomePark.
toBool());
2296 else if (iface == process()->captureInterface())
2298 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
2299 if (hasCoolerControl.
isValid())
2301 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
2306void Scheduler::setWeatherStatus(ISD::Weather::Status status)
2308 TEST_PRINT(stderr,
"sch%d @@@setWeatherStatus(%d)\n", __LINE__,
static_cast<int>(status));
2309 ISD::Weather::Status newStatus = status;
2314 case ISD::Weather::WEATHER_OK:
2315 statusString =
i18n(
"Weather conditions are OK.");
2318 case ISD::Weather::WEATHER_WARNING:
2319 statusString =
i18n(
"Warning: weather conditions are in the WARNING zone.");
2322 case ISD::Weather::WEATHER_ALERT:
2323 statusString =
i18n(
"Caution: weather conditions are in the DANGER zone!");
2330 qCDebug(KSTARS_EKOS_SCHEDULER) << statusString;
2332 if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_OK)
2333 weatherLabel->setPixmap(
2335 .pixmap(
QSize(32, 32)));
2336 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_WARNING)
2338 weatherLabel->setPixmap(
2340 .pixmap(
QSize(32, 32)));
2341 KSNotification::event(
QLatin1String(
"WeatherWarning"),
i18n(
"Weather conditions in warning zone"),
2342 KSNotification::Scheduler, KSNotification::Warn);
2344 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_ALERT)
2346 weatherLabel->setPixmap(
2348 .pixmap(QSize(32, 32)));
2349 KSNotification::event(QLatin1String(
"WeatherAlert"),
2350 i18n(
"Weather conditions are critical. Observatory shutdown is imminent"), KSNotification::Scheduler,
2351 KSNotification::Alert);
2355 .pixmap(QSize(32, 32)));
2357 weatherLabel->show();
2358 weatherLabel->setToolTip(statusString);
2360 process()->appendLogText(statusString);
2362 emit weatherChanged(moduleState()->weatherStatus());
2369 schedulerWeather->setEnabled(
false);
2370 weatherLabel->hide();
2373 changeSleepLabel(
i18n(
"Scheduler is in sleep mode"));
2380 case SCHEDULER_RUNNING:
2382 pi->startAnimation();
2385 startB->setToolTip(
i18n(
"Stop Scheduler"));
2386 pauseB->setEnabled(
true);
2387 pauseB->setChecked(
false);
2390 queueLoadB->setEnabled(
false);
2391 setJobManipulation(
true,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
2393 evaluateOnlyB->setEnabled(
false);
2394 startupB->setEnabled(
false);
2395 shutdownB->setEnabled(
false);
2402 emit newStatus(newState);
2407 pauseB->setCheckable(
true);
2408 pauseB->setChecked(
true);
2411void Scheduler::handleJobsUpdated(
QJsonArray jobsList)
2416 emit jobsUpdated(jobsList);
2422 return assistant->importMosaic(payload);
2425void Scheduler::startupStateChanged(StartupState state)
2427 jobStatus->setText(startupStateString(state));
2429 switch (moduleState()->startupState())
2434 case STARTUP_COMPLETE:
2436 process()->appendLogText(
i18n(
"Manual startup procedure completed successfully."));
2440 process()->appendLogText(
i18n(
"Manual startup procedure terminated due to errors."));
2448void Scheduler::shutdownStateChanged(ShutdownState state)
2450 if (state == SHUTDOWN_COMPLETE || state == SHUTDOWN_IDLE
2451 || state == SHUTDOWN_ERROR)
2454 pi->stopAnimation();
2459 if (state == SHUTDOWN_IDLE)
2460 jobStatus->setText(
i18n(
"Idle"));
2462 jobStatus->setText(shutdownStateString(state));
2464void Scheduler::ekosStateChanged(EkosState state)
2466 if (state == EKOS_IDLE)
2468 jobStatus->setText(
i18n(
"Idle"));
2469 pi->stopAnimation();
2472 jobStatus->setText(ekosStateString(state));
2474void Scheduler::indiStateChanged(INDIState state)
2476 if (state == INDI_IDLE)
2478 jobStatus->setText(
i18n(
"Idle"));
2479 pi->stopAnimation();
2482 jobStatus->setText(indiStateString(state));
2484 refreshOpticalTrain();
2487void Scheduler::indiCommunicationStatusChanged(CommunicationStatus status)
2489 if (status == Success)
2490 refreshOpticalTrain();
2492void Scheduler::parkWaitStateChanged(ParkWaitState state)
2494 jobStatus->setText(parkWaitStateString(state));
2497SchedulerJob *Scheduler::activeJob()
2499 return moduleState()->activeJob();
2502void Scheduler::loadGlobalSettings()
2507 QVariantMap settings;
2511 key = oneWidget->objectName();
2512 value = Options::self()->property(key.
toLatin1());
2513 if (value.
isValid() && oneWidget->count() > 0)
2515 oneWidget->setCurrentText(value.
toString());
2516 settings[key] = value;
2519 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2525 key = oneWidget->objectName();
2526 value = Options::self()->property(key.
toLatin1());
2529 oneWidget->setValue(value.
toDouble());
2530 settings[key] = value;
2533 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2539 key = oneWidget->objectName();
2540 value = Options::self()->property(key.
toLatin1());
2543 oneWidget->setValue(value.
toInt());
2544 settings[key] = value;
2547 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2553 key = oneWidget->objectName();
2554 value = Options::self()->property(key.
toLatin1());
2557 oneWidget->setChecked(value.
toBool());
2558 settings[key] = value;
2561 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2567 key = oneWidget->objectName();
2568 value = Options::self()->property(key.
toLatin1());
2571 oneWidget->setText(value.
toString());
2572 settings[key] = value;
2574 if (key ==
"sequenceEdit")
2576 else if (key ==
"schedulerStartupScript")
2578 else if (key ==
"schedulerShutdownScript")
2582 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2588 key = oneWidget->objectName();
2589 value = Options::self()->property(key.
toLatin1());
2592 oneWidget->setChecked(value.
toBool());
2593 settings[key] = value;
2600 key = oneWidget->objectName();
2601 value = Options::self()->property(key.
toLatin1());
2605 settings[key] = value;
2611 m_GlobalSettings = m_Settings = settings;
2614void Scheduler::syncSettings()
2616 QDoubleSpinBox *dsb =
nullptr;
2617 QSpinBox *sb =
nullptr;
2618 QCheckBox *cb =
nullptr;
2619 QRadioButton *rb =
nullptr;
2620 QComboBox *cbox =
nullptr;
2621 QLineEdit *lineedit =
nullptr;
2622 QDateTimeEdit *datetimeedit =
nullptr;
2626 bool removeKey =
false;
2631 value = dsb->
value();
2637 value = sb->
value();
2665 value = lineedit->
text();
2674 Options::self()->setProperty(key.
toLatin1(), value);
2677 m_Settings.remove(key);
2679 m_Settings[key] = value;
2680 m_GlobalSettings[key] = value;
2682 m_DebounceTimer.start();
2690 emit settingsUpdated(getAllSettings());
2691 Options::self()->save();
2697QVariantMap Scheduler::getAllSettings()
const
2699 QVariantMap settings;
2703 settings.insert(oneWidget->objectName(), oneWidget->currentText());
2707 settings.insert(oneWidget->objectName(), oneWidget->value());
2711 settings.insert(oneWidget->objectName(), oneWidget->value());
2715 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2721 if (!oneWidget->objectName().startsWith(
"qt_"))
2722 settings.insert(oneWidget->objectName(), oneWidget->text());
2727 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2732 settings.insert(oneWidget->objectName(), oneWidget->dateTime().toString(
Qt::ISODate));
2741void Scheduler::setAllSettings(
const QVariantMap &settings)
2745 disconnectSettings();
2747 for (
auto &name : settings.keys())
2753 syncControl(settings, name, comboBox);
2761 syncControl(settings, name, doubleSpinBox);
2769 syncControl(settings, name, spinBox);
2777 syncControl(settings, name, checkbox);
2785 syncControl(settings, name, lineedit);
2787 if (name ==
"sequenceEdit")
2789 else if (name ==
"fitsEdit")
2791 else if (name ==
"schedulerStartupScript")
2793 else if (name ==
"schedulerShutdownScript")
2803 syncControl(settings, name, radioButton);
2810 syncControl(settings, name, datetimeedit);
2815 m_Settings = settings;
2824bool Scheduler::syncControl(
const QVariantMap &settings,
const QString &key, QWidget * widget)
2826 QSpinBox *pSB =
nullptr;
2827 QDoubleSpinBox *pDSB =
nullptr;
2828 QCheckBox *pCB =
nullptr;
2829 QComboBox *pComboBox =
nullptr;
2830 QLineEdit *pLineEdit =
nullptr;
2831 QRadioButton *pRadioButton =
nullptr;
2832 QDateTimeEdit *pDateTimeEdit =
nullptr;
2837 const int value = settings[key].toInt(&ok);
2846 const double value = settings[key].toDouble(&ok);
2855 const bool value = settings[key].toBool();
2863 const QString value = settings[key].toString();
2869 const auto value = settings[key].toString();
2875 const bool value = settings[key].toBool();
2890void Scheduler::refreshOpticalTrain()
2892 opticalTrainCombo->blockSignals(
true);
2893 opticalTrainCombo->clear();
2894 opticalTrainCombo->addItem(
"--");
2895 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
2896 opticalTrainCombo->blockSignals(
false);
2899void Scheduler::connectSettings()
2925 if (!oneWidget->objectName().startsWith(
"qt_"))
2934void Scheduler::disconnectSettings()
2965void Scheduler::handleAltitudeGraph(
int index)
2967 if (!m_altitudeGraph)
2968 m_altitudeGraph =
new SchedulerAltitudeGraph;
2970 if (index < 0 || index >= moduleState()->jobs().
size())
2972 auto job = moduleState()->jobs().at(index);
2974 QDateTime now = SchedulerModuleState::getLocalTime(), start,
end;
2975 QDateTime nextDawn, nextDusk;
2976 SchedulerModuleState::calculateDawnDusk(now, nextDawn, nextDusk);
2978 QVector<double> times, alts;
2979 QDateTime plotStart = (nextDusk < nextDawn) ? nextDusk : nextDusk.addDays(-1);
2988 plotStart = plotStart.
addSecs(-1 * 3600);
2990 auto plotEnd = nextDawn.
addSecs(1 * 3600);
2991 while (t.secsTo(plotEnd) > 0)
2993 double alt = SchedulerUtils::findAltitude(job->getTargetCoords(), t);
2995 double hour = midnight.
secsTo(t) / 3600.0;
2997 t = t.addSecs(60 * 10);
3000 KStarsDateTime ut = SchedulerModuleState::getGeo()->LTtoUT(KStarsDateTime(midnight));
3001 KSAlmanac ksal(ut, SchedulerModuleState::getGeo());
3002 m_altitudeGraph->setTitle(job->getName());
3003 m_altitudeGraph->plot(SchedulerModuleState::getGeo(), &ksal, times, alts);
3006 auto startTime = (job->getState() ==
SCHEDJOB_BUSY) ? job->getStateTime() : job->getStartupTime();
3007 if (startTime.isValid() && startTime < plotEnd && job->getStopTime().
isValid())
3009 auto stopTime = job->getStopTime();
3010 if (startTime < plotStart) startTime = plotStart;
3011 if (stopTime > plotEnd)
3014 QVector<double> runTimes, runAlts;
3016 while (t.secsTo(stopTime) > 0)
3018 double alt = SchedulerUtils::findAltitude(job->getTargetCoords(), t);
3020 double hour = midnight.
secsTo(t) / 3600.0;
3022 t = t.addSecs(60 * 10);
3025 m_altitudeGraph->plot(SchedulerModuleState::getGeo(), &ksal, runTimes, runAlts,
true);
3027 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.
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)
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