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);
150 queueTable->setToolTip(
151 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."));
152 QTableWidgetItem *statusHeader = queueTable->horizontalHeaderItem(SCHEDCOL_STATUS);
153 QTableWidgetItem *altitudeHeader = queueTable->horizontalHeaderItem(SCHEDCOL_ALTITUDE);
154 QTableWidgetItem *startupHeader = queueTable->horizontalHeaderItem(SCHEDCOL_STARTTIME);
155 QTableWidgetItem *completionHeader = queueTable->horizontalHeaderItem(SCHEDCOL_ENDTIME);
156 QTableWidgetItem *captureCountHeader = queueTable->horizontalHeaderItem(SCHEDCOL_CAPTURES);
158 if (statusHeader !=
nullptr)
159 statusHeader->
setToolTip(
i18n(
"Current status of the job, managed by the Scheduler.\n"
160 "If invalid, the Scheduler was not able to find a proper observation time for the target.\n"
161 "If aborted, the Scheduler missed the scheduled time or encountered transitory issues and will reschedule the job.\n"
162 "If complete, the Scheduler verified that all sequence captures requested were stored, including repeats."));
163 if (altitudeHeader !=
nullptr)
164 altitudeHeader->
setToolTip(
i18n(
"Current altitude of the target of the job.\n"
165 "A rising target is indicated with an arrow going up.\n"
166 "A setting target is indicated with an arrow going down."));
167 if (startupHeader !=
nullptr)
168 startupHeader->
setToolTip(
i18n(
"Startup time of the job, as estimated by the Scheduler.\n"
169 "The altitude at startup, if available, is displayed too.\n"
170 "Fixed time from user or culmination time is marked with a chronometer symbol."));
171 if (completionHeader !=
nullptr)
172 completionHeader->
setToolTip(
i18n(
"Completion time for the job, as estimated by the Scheduler.\n"
173 "You may specify a fixed time to limit duration of looping jobs. "
174 "A warning symbol indicates the altitude at completion may cause the job to abort before completion.\n"));
175 if (captureCountHeader !=
nullptr)
176 captureCountHeader->
setToolTip(
i18n(
"Count of captures stored for the job, based on its sequence job.\n"
177 "This is a summary, additional specific frame types may be required to complete the job."));
183 removeFromQueueB->setToolTip(
184 i18n(
"Remove selected job from the observation list.\nJob properties are copied in the edition fields before removal."));
188 queueUpB->setToolTip(
i18n(
"Move selected job one line up in the list.\n"));
191 queueDownB->setToolTip(
i18n(
"Move selected job one line down in the list.\n"));
195 evaluateOnlyB->setToolTip(
i18n(
"Reset state and force reevaluation of all observation jobs."));
198 sortJobsB->setToolTip(
199 i18n(
"Reset state and sort observation jobs per altitude and movement in sky, using the start time of the first job.\n"
200 "This action sorts setting targets before rising targets, and may help scheduling when starting your observation.\n"
201 "Note the algorithm first calculates all altitudes using the same time, then evaluates jobs."));
206 positionAngleSpin->setSpecialValueText(
"--");
221 selectShutdownScriptB->setIcon(
236 schedulerRepeatEverything->setEnabled(Options::rememberJobProgress() ==
false);
237 executionSequenceLimit->setEnabled(Options::rememberJobProgress() ==
false);
238 executionSequenceLimit->setValue(Options::schedulerExecutionSequencesLimit());
241 leadFollowerSelectionCB->setEnabled(
false);
251 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated,
this, &Scheduler::refreshOpticalTrain);
255 mosaicB->setDown(checked);
290 pauseB->setCheckable(
false);
309 connect(moduleState().data(), &SchedulerModuleState::ekosStateChanged,
this, &Scheduler::ekosStateChanged);
310 connect(moduleState().data(), &SchedulerModuleState::indiStateChanged,
this, &Scheduler::indiStateChanged);
311 connect(moduleState().data(), &SchedulerModuleState::indiCommunicationStatusChanged,
this,
312 &Scheduler::indiCommunicationStatusChanged);
314 connect(moduleState().data(), &SchedulerModuleState::startupStateChanged,
this, &Scheduler::startupStateChanged);
315 connect(moduleState().data(), &SchedulerModuleState::shutdownStateChanged,
this, &Scheduler::shutdownStateChanged);
316 connect(moduleState().data(), &SchedulerModuleState::parkWaitStateChanged,
this, &Scheduler::parkWaitStateChanged);
317 connect(moduleState().data(), &SchedulerModuleState::profilesChanged,
this, &Scheduler::updateProfiles);
319 connect(moduleState().data(), &SchedulerModuleState::jobStageChanged,
this, &Scheduler::updateJobStageUI);
321 connect(moduleState().data(), &SchedulerModuleState::currentProfileChanged,
this, [&]()
323 schedulerProfileCombo->setCurrentText(moduleState()->currentProfile());
328 connect(process().data(), &SchedulerProcess::shutdownStarted,
this, &Scheduler::handleShutdownStarted);
330 connect(process().data(), &SchedulerProcess::jobsUpdated,
this, &Scheduler::handleJobsUpdated);
331 connect(process().data(), &SchedulerProcess::targetDistance,
this, &Scheduler::targetDistance);
336 connect(process().data(), &SchedulerProcess::jobStarted,
this, &Scheduler::jobStarted);
337 connect(process().data(), &SchedulerProcess::jobEnded,
this, &Scheduler::jobEnded);
338 connect(process().data(), &SchedulerProcess::syncGreedyParams,
this, &Scheduler::syncGreedyParams);
340 connect(process().data(), &SchedulerProcess::changeSleepLabel,
this, &Scheduler::changeSleepLabel);
343 connect(process().data(), &SchedulerProcess::newWeatherStatus,
this, &Scheduler::setWeatherStatus);
356 Options::setErrorHandlingStrategy(strategy);
357 errorHandlingStrategyDelay->setEnabled(strategy != ERROR_DONT_RESTART);
361 Options::setErrorHandlingStrategyDelay(value);
365 if (Options::schedulerAlgorithm() != ALGORITHM_GREEDY)
367 process()->appendLogText(
368 i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
369 Options::setSchedulerAlgorithm(ALGORITHM_GREEDY);
373 setAlgorithm(Options::schedulerAlgorithm());
379 center.catalogueCoord(KStarsData::Instance()->updateNum()->julianDay());
380 raBox->show(
center.ra0());
381 decBox->show(
center.dec0());
386 if (!m_SequenceEditor)
387 m_SequenceEditor.
reset(
new SequenceEditor(
this));
389 m_SequenceEditor->show();
390 m_SequenceEditor->raise();
397 emit jobsUpdated(moduleState()->getJSONJobs());
400 moduleState()->calculateDawnDusk();
401 process()->loadProfiles();
405 loadGlobalSettings();
407 refreshOpticalTrain();
410QString Scheduler::getCurrentJobName()
412 return (activeJob() !=
nullptr ? activeJob()->getName() :
"");
423 m_OpsOffsetSettings =
new OpsOffsetSettings();
427 m_OpsAlignmentSettings =
new OpsAlignmentSettings();
428 page = dialog->
addPage(m_OpsAlignmentSettings,
i18n(
"Alignment"),
nullptr,
i18n(
"Scheduler Alignment"),
false);
432 m_OpsJobsSettings =
new OpsJobsSettings();
433 page = dialog->
addPage(m_OpsJobsSettings,
i18n(
"Jobs"),
nullptr,
i18n(
"Scheduler Jobs"),
false);
436 m_OpsCleanupSettings =
new OpsCleanupSettings();
437 page = dialog->
addPage(m_OpsCleanupSettings,
i18n(
"Cleanup"),
nullptr,
i18n(
"Scheduler Cleanup"),
false);
444 if (enable == jobChangesAreWatched)
456 schedulerStartupScript,
457 schedulerShutdownScript
468 schedulerProfileCombo,
470 leadFollowerSelectionCB
476 errorHandlingButtonGroup,
478 constraintButtonGroup,
479 completionButtonGroup,
480 startupProcedureButtonGroup,
481 shutdownProcedureGroup
486 errorHandlingRescheduleErrorsCB
491 schedulerExecutionSequencesLimit,
492 errorHandlingStrategyDelay
497 schedulerMoonSeparationValue,
498 schedulerAltitudeValue,
513 for (
auto *
const control : lineEdits)
518 for (
auto *
const control : dateEdits)
523 for (
auto *
const control : comboBoxes)
525 if (control == leadFollowerSelectionCB)
527 this, [
this](
int pos)
529 setJobManipulation(queueUpB->isEnabled() || queueDownB->isEnabled(), removeFromQueueB->isEnabled(),
pos == INDEX_LEAD);
538 for (
auto *
const control : buttonGroups)
539#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
547 for (
auto *
const control : buttons)
552 for (
auto *
const control : spinBoxes)
557 for (
auto *
const control : dspinBoxes)
570 for (
auto *
const control : lineEdits)
572 for (
auto *
const control : dateEdits)
574 for (
auto *
const control : comboBoxes)
576 for (
auto *
const control : buttons)
578 for (
auto *
const control : buttonGroups)
579#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
584 for (
auto *
const control : spinBoxes)
586 for (
auto *
const control : dspinBoxes)
590 jobChangesAreWatched = enable;
595 schedulerRepeatEverything->
setEnabled(Options::rememberJobProgress() ==
false);
596 executionSequenceLimit->setEnabled(Options::rememberJobProgress() ==
false);
601 if (FindDialog::Instance()->execWithParent(Ekos::Manager::Instance()) ==
QDialog::Accepted)
608void Scheduler::addObject(
SkyObject *
object)
610 if (
object !=
nullptr)
614 if (object->
name() ==
"star")
622 nameEdit->setText(finalObjectName);
623 raBox->show(object->
ra0());
624 decBox->show(object->
dec0());
633 "FITS (*.fits *.fit);;XISF (*.xisf)");
637 processFITSSelection(url);
640void Scheduler::processFITSSelection(
const QUrl &url)
650 const QString filename = fitsEdit->text();
652 double ra = 0, dec = 0;
654 char comment[128], error_status[512];
655 fitsfile *fptr =
nullptr;
657 if (fits_open_diskfile(&fptr, filename.
toLatin1(), READONLY, &status))
659 fits_report_error(stderr, status);
660 fits_get_errstatus(status, error_status);
666 if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status))
668 fits_report_error(stderr, status);
669 fits_get_errstatus(status, error_status);
675 char objectra_str[32] = {0};
676 if (fits_read_key(fptr, TSTRING,
"OBJCTRA", objectra_str, comment, &status))
678 if (fits_read_key(fptr, TDOUBLE,
"RA", &ra, comment, &status))
680 fits_report_error(stderr, status);
681 fits_get_errstatus(status, error_status);
682 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTRA (%1).",
QString(error_status)));
694 char objectde_str[32] = {0};
695 if (fits_read_key(fptr, TSTRING,
"OBJCTDEC", objectde_str, comment, &status))
697 if (fits_read_key(fptr, TDOUBLE,
"DEC", &dec, comment, &status))
699 fits_report_error(stderr, status);
700 fits_get_errstatus(status, error_status);
701 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTDEC (%1).",
QString(error_status)));
715 char object_str[256] = {0};
716 if (fits_read_key(fptr, TSTRING,
"OBJECT", object_str, comment, &status))
719 nameEdit->setText(info.completeBaseName());
723 nameEdit->setText(object_str);
744 i18n(
"Ekos Sequence Queue (*.esq)"));
752 "Select Startup Script"),
754 i18n(
"Script (*)")));
755 if (moduleState()->startupScriptURL().isEmpty())
760 moduleState()->setDirty(
true);
761 schedulerStartupScript->setText(moduleState()->startupScriptURL().toLocalFile());
767 "Select Shutdown Script"),
769 i18n(
"Script (*)")));
770 if (moduleState()->shutdownScriptURL().isEmpty())
775 moduleState()->setDirty(
true);
776 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toLocalFile());
781 if (0 <= jobUnderEdit)
784 job = moduleState()->jobs().at(jobUnderEdit);
794 int currentRow = moduleState()->currentPosition();
798 currentRow = queueTable->rowCount();
806 if (moduleState()->jobs().count() > currentRow)
807 moduleState()->setCurrentPosition(currentRow);
810 emit jobsUpdated(moduleState()->getJSONJobs());
817 auto job = moduleState()->jobs().at(index);
824 emit jobsUpdated(moduleState()->getJSONJobs());
831 if (nameEdit->text().isEmpty())
833 process()->appendLogText(
i18n(
"Warning: Target name is required."));
837 if (sequenceEdit->text().isEmpty())
839 process()->appendLogText(
i18n(
"Warning: Sequence file is required."));
844 if ((raBox->isEmpty() || decBox->isEmpty()) && fitsURL.
isEmpty())
846 process()->appendLogText(
i18n(
"Warning: Target coordinates are required."));
850 bool raOk =
false, decOk =
false;
851 dms ra(raBox->createDms(&raOk));
852 dms dec(decBox->createDms(&decOk));
856 process()->appendLogText(
i18n(
"Warning: RA value %1 is invalid.", raBox->text()));
862 process()->appendLogText(
i18n(
"Warning: DEC value %1 is invalid.", decBox->text()));
872 if (asapConditionR->isChecked())
873 startCondition = START_ASAP;
876 if (schedulerCompleteSequences->isChecked())
877 stopCondition = FINISH_SEQUENCE;
878 else if (schedulerRepeatSequences->isChecked())
879 stopCondition = FINISH_REPEAT;
880 else if (schedulerUntilTerminated->isChecked())
881 stopCondition = FINISH_LOOP;
883 double altConstraint = SchedulerJob::UNDEFINED_ALTITUDE;
884 if (schedulerAltitude->isChecked())
885 altConstraint = schedulerAltitudeValue->value();
887 double moonConstraint = -1;
888 if (schedulerMoonSeparation->isChecked())
889 moonConstraint = schedulerMoonSeparationValue->value();
891 QString train = opticalTrainCombo->currentText() ==
"--" ?
"" : opticalTrainCombo->currentText();
895 SchedulerUtils::setupJob(*job, nameEdit->text(), leadFollowerSelectionCB->currentIndex() == INDEX_LEAD, groupEdit->text(),
897 KStarsData::Instance()->
ut().djd(),
898 positionAngleSpin->value(), sequenceURL, fitsURL,
900 startCondition, startupTimeEdit->dateTime(),
901 stopCondition, schedulerUntilValue->dateTime(), schedulerExecutionSequencesLimit->value(),
905 schedulerWeather->isChecked(),
906 schedulerTwilight->isChecked(),
907 schedulerHorizon->isChecked(),
909 schedulerTrackStep->isChecked(),
910 schedulerFocusStep->isChecked(),
911 schedulerAlignStep->isChecked(),
912 schedulerGuideStep->isChecked());
924 int currentRow = moduleState()->currentPosition() + 1;
929 if (0 <= jobUnderEdit)
932 if (jobUnderEdit != currentRow - 1)
934 qCWarning(KSTARS_EKOS_SCHEDULER) <<
"BUG: the observation job under edit does not match the selected row in the job table.";
938 job = moduleState()->jobs().at(jobUnderEdit);
951 job =
new SchedulerJob();
961 moduleState()->mutlableJobs().insert(currentRow, job);
967 job->setLeadJob(moduleState()->findLead(currentRow - 1));
968 moduleState()->refreshFollowerLists();
976 foreach (SchedulerJob *a_job, moduleState()->jobs())
978 if (a_job == job || !a_job->isLead())
982 else if (a_job->getName() == job->getName())
984 int const a_job_row = moduleState()->jobs().indexOf(a_job);
987 process()->appendLogText(
i18n(
"Warning: job '%1' at row %2 has a duplicate target at row %3, "
988 "the scheduler may consider the same storage for captures.",
989 job->getName(), currentRow, a_job_row));
992 if (a_job->getSequenceFile() == job->getSequenceFile())
994 if (a_job->getRepeatsRequired() == job->getRepeatsRequired() && Options::rememberJobProgress())
995 process()->appendLogText(
i18n(
"Warning: jobs '%1' at row %2 and %3 probably require a different repeat count "
996 "as currently they will complete simultaneously after %4 batches (or disable option 'Remember job progress')",
997 job->getName(), currentRow, a_job_row, job->getRepeatsRequired()));
1001 if (++numWarnings >= 1)
1003 process()->appendLogText(
i18n(
"Skipped checking for duplicates."));
1013 queueSaveAsB->setEnabled(
true);
1014 queueSaveB->setEnabled(
true);
1015 startB->setEnabled(
true);
1016 evaluateOnlyB->setEnabled(
true);
1018 checkJobInputComplete();
1020 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 was saved.").
arg(job->getName()).
arg(currentRow + 1);
1024 if (SCHEDULER_LOADING != moduleState()->schedulerState())
1026 process()->evaluateJobs(
true);
1032 nameEdit->setText(job->getName());
1033 groupEdit->setText(job->getGroup());
1035 raBox->show(job->getTargetCoords().ra0());
1036 decBox->show(job->getTargetCoords().dec0());
1039 fitsURL = job->getFITSFile().
isEmpty() ?
QUrl() : job->getFITSFile();
1042 schedulerTrackStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_TRACK);
1043 schedulerFocusStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_FOCUS);
1044 schedulerAlignStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_ALIGN);
1045 schedulerGuideStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_GUIDE);
1047 switch (job->getFileStartupCondition())
1050 asapConditionR->setChecked(
true);
1054 startupTimeConditionR->setChecked(
true);
1055 startupTimeEdit->setDateTime(job->getStartupTime());
1059 if (job->getMinAltitude())
1061 schedulerAltitude->setChecked(
true);
1062 schedulerAltitudeValue->setValue(job->getMinAltitude());
1066 schedulerAltitude->setChecked(
false);
1067 schedulerAltitudeValue->setValue(DEFAULT_MIN_ALTITUDE);
1070 if (job->getMinMoonSeparation() >= 0)
1072 schedulerMoonSeparation->setChecked(
true);
1073 schedulerMoonSeparationValue->setValue(job->getMinMoonSeparation());
1077 schedulerMoonSeparation->setChecked(
false);
1078 schedulerMoonSeparationValue->setValue(DEFAULT_MIN_MOON_SEPARATION);
1081 schedulerWeather->setChecked(job->getEnforceWeather());
1083 schedulerTwilight->blockSignals(
true);
1084 schedulerTwilight->setChecked(job->getEnforceTwilight());
1085 schedulerTwilight->blockSignals(
false);
1087 schedulerHorizon->blockSignals(
true);
1088 schedulerHorizon->setChecked(job->getEnforceArtificialHorizon());
1089 schedulerHorizon->blockSignals(
false);
1093 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1097 leadFollowerSelectionCB->setCurrentIndex(INDEX_FOLLOWER);
1100 if (job->getOpticalTrain().
isEmpty())
1101 opticalTrainCombo->setCurrentIndex(0);
1103 opticalTrainCombo->setCurrentText(job->getOpticalTrain());
1105 sequenceURL = job->getSequenceFile();
1108 positionAngleSpin->setValue(job->getPositionAngle());
1110 switch (job->getCompletionCondition())
1112 case FINISH_SEQUENCE:
1113 schedulerCompleteSequences->setChecked(
true);
1117 schedulerRepeatSequences->setChecked(
true);
1118 schedulerExecutionSequencesLimit->setValue(job->getRepeatsRequired());
1122 schedulerUntilTerminated->setChecked(
true);
1126 schedulerUntil->setChecked(
true);
1127 schedulerUntilValue->setDateTime(job->getFinishAtTime());
1137 schedulerParkDome->setChecked(Options::schedulerParkDome());
1138 schedulerParkMount->setChecked(Options::schedulerParkMount());
1139 schedulerCloseDustCover->setChecked(Options::schedulerCloseDustCover());
1140 schedulerWarmCCD->setChecked(Options::schedulerWarmCCD());
1141 schedulerUnparkDome->setChecked(Options::schedulerUnparkDome());
1142 schedulerUnparkMount->setChecked(Options::schedulerUnparkMount());
1143 schedulerOpenDustCover->setChecked(Options::schedulerOpenDustCover());
1145 errorHandlingStrategyDelay->setValue(Options::errorHandlingStrategyDelay());
1146 errorHandlingRescheduleErrorsCB->setChecked(Options::rescheduleErrors());
1148 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toString(
QUrl::PreferLocalFile));
1150 if (process()->captureInterface() !=
nullptr)
1152 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
1153 if (hasCoolerControl.
isValid())
1155 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
1156 moduleState()->setCaptureReady(
true);
1164 if (job ==
nullptr && moduleState()->jobs().
size() > 0)
1166 int const currentRow = moduleState()->currentPosition();
1167 if (0 <= currentRow && currentRow < moduleState()->jobs().
size())
1168 job = moduleState()->jobs().at(currentRow);
1172 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Cannot update night time, no matching job found at line" << currentRow;
1177 QDateTime const dawn = job ? job->getDawnAstronomicalTwilight() : moduleState()->Dawn();
1178 QDateTime const dusk = job ? job->getDuskAstronomicalTwilight() : moduleState()->Dusk();
1180 QChar const warning(dawn == dusk ? 0x26A0 :
'-');
1184bool Scheduler::modifyJob(
int index)
1192 queueTable->selectRow(index);
1193 auto modelIndex = queueTable->model()->index(index, 0);
1200 if (jobUnderEdit == i.
row())
1203 SchedulerJob *
const job = moduleState()->jobs().at(i.
row());
1218 startB->setEnabled(
false);
1219 evaluateOnlyB->setEnabled(
false);
1224 jobUnderEdit = i.
row();
1225 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is currently edited.").
arg(job->getName()).
arg(
1235 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.
fileName());
1240 Q_UNUSED(deselected)
1243 if (jobChangesAreWatched ==
false || selected.
empty())
1249 if ((current.
row() + 1) > moduleState()->jobs().
size())
1251 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Unexpected row number" << current.
row() <<
"- ignoring.";
1254 moduleState()->setCurrentPosition(current.
row());
1255 SchedulerJob *
const job = moduleState()->jobs().at(current.
row());
1259 if (jobUnderEdit < 0)
1261 else if (jobUnderEdit != current.
row())
1264 process()->appendLogText(
i18n(
"Stop editing of job #%1, resetting to original value.", jobUnderEdit + 1));
1269 else nightTime->setText(
"-");
1278 handleAltitudeGraph(index.
row());
1282 if (index.
isValid() && index.
row() < moduleState()->jobs().count())
1293 addToQueueB->setToolTip(
i18n(
"Use edition fields to create a new job in the observation list."));
1299 addToQueueB->setToolTip(
i18n(
"Apply job changes."));
1302 checkJobInputComplete();
1309 int const currentRow = moduleState()->currentPosition();
1310 if (currentRow >= 0)
1312 SchedulerJob *currentJob = moduleState()->jobs().at(currentRow);
1314 queueUpB->setEnabled(0 < currentRow &&
1315 (currentJob->isLead() || (currentRow > 1 && moduleState()->findLead(currentRow - 2) !=
nullptr)));
1317 queueDownB->setEnabled(currentRow < queueTable->rowCount() - 1 &&
1318 (moduleState()->findLead(currentRow + 1,
false) !=
nullptr));
1323 queueUpB->setEnabled(
false);
1324 queueDownB->setEnabled(
false);
1326 sortJobsB->setEnabled(can_reorder);
1327 removeFromQueueB->setEnabled(can_delete);
1329 nameEdit->setEnabled(is_lead);
1330 selectObjectB->setEnabled(is_lead);
1331 targetStarLabel->setVisible(is_lead);
1332 raBox->setEnabled(is_lead);
1333 decBox->setEnabled(is_lead);
1334 copySkyCenterB->setEnabled(is_lead);
1335 schedulerProfileCombo->setEnabled(is_lead);
1336 fitsEdit->setEnabled(is_lead);
1337 selectFITSB->setEnabled(is_lead);
1338 groupEdit->setEnabled(is_lead);
1339 schedulerTrackStep->setEnabled(is_lead);
1340 schedulerFocusStep->setEnabled(is_lead);
1341 schedulerAlignStep->setEnabled(is_lead);
1342 schedulerGuideStep->setEnabled(is_lead);
1343 startupGroup->setEnabled(is_lead);
1344 contraintsGroup->setEnabled(is_lead);
1347 leadFollowerSelectionCB->setEnabled(moduleState()->findLead(queueTable->currentRow()) !=
nullptr);
1348 if (leadFollowerSelectionCB->isEnabled() ==
false)
1349 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1355 foreach (SchedulerJob* job, moduleState()->jobs())
1356 if (!reordered_sublist.
contains(job))
1357 reordered_sublist.
append(job);
1359 if (moduleState()->jobs() != reordered_sublist)
1362 int const selectedRow = moduleState()->currentPosition();
1363 SchedulerJob *
const selectedJob = 0 <= selectedRow ? moduleState()->jobs().at(selectedRow) :
nullptr;
1366 moduleState()->setJobs(reordered_sublist);
1369 for (SchedulerJob *job : moduleState()->jobs())
1373 if (
nullptr != selectedJob)
1374 moduleState()->setCurrentPosition(moduleState()->jobs().indexOf(selectedJob));
1383 int const rowCount = queueTable->rowCount();
1384 int const currentRow = queueTable->currentRow();
1386 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1388 if (moduleState()->jobs().at(currentRow)->isLead())
1390 int const rows = 1 + job->followerJobs().
count();
1392 if (currentRow - rows < 0)
1396 destinationRow = currentRow - 1 - moduleState()->jobs().at(currentRow - rows)->followerJobs().count();
1399 destinationRow = currentRow - 1;
1402 if (currentRow < 0 || rowCount <= 1 || destinationRow < 0)
1405 if (moduleState()->jobs().at(currentRow)->isLead())
1408 moduleState()->mutlableJobs().removeOne(job);
1409 for (
auto follower : job->followerJobs())
1410 moduleState()->mutlableJobs().removeOne(follower);
1413 moduleState()->mutlableJobs().insert(destinationRow++, job);
1415 for (
auto follower : job->followerJobs())
1416 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1418 for (
int i = currentRow; i > destinationRow; i--)
1421 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1426#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1427 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1429 moduleState()->jobs().swap(currentRow, destinationRow);
1437 moduleState()->setCurrentPosition(destinationRow);
1439 SchedulerJob *newLead = moduleState()->findLead(destinationRow,
true);
1440 if (newLead !=
nullptr)
1442 job->setLeadJob(newLead);
1443 moduleState()->refreshFollowerLists();
1447 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1450 moduleState()->setDirty(
true);
1451 process()->evaluateJobs(
true);
1456 int const rowCount = queueTable->rowCount();
1457 int const currentRow = queueTable->currentRow();
1459 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1461 if (moduleState()->jobs().at(currentRow)->isLead())
1463 int const rows = 1 + job->followerJobs().
count();
1465 if (currentRow + rows >= moduleState()->jobs().count())
1469 destinationRow = currentRow + 1 + moduleState()->jobs().at(currentRow + rows)->followerJobs().count();
1472 destinationRow = currentRow + 1;
1475 if (currentRow < 0 || rowCount <= 1 || destinationRow >= rowCount)
1478 if (moduleState()->jobs().at(currentRow)->isLead())
1481 moduleState()->mutlableJobs().removeOne(job);
1482 for (
auto follower : job->followerJobs())
1483 moduleState()->mutlableJobs().removeOne(follower);
1486 moduleState()->mutlableJobs().insert(destinationRow++, job);
1488 for (
auto follower : job->followerJobs())
1489 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1491 for (
int i = currentRow; i < destinationRow; i++)
1494 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1499#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1500 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1502 moduleState()->mutlableJobs().swap(currentRow, destinationRow);
1508 moduleState()->setCurrentPosition(destinationRow);
1510 if (moduleState()->jobs().at(currentRow)->isLead())
1512 job->setLeadJob(moduleState()->jobs().at(currentRow));
1513 moduleState()->refreshFollowerLists();
1517 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1520 moduleState()->setDirty(
true);
1521 process()->evaluateJobs(
true);
1529 for (
auto onejob : moduleState()->jobs())
1535 const int row = moduleState()->jobs().indexOf(job);
1540 if (row >= queueTable->rowCount())
1543 QTableWidgetItem *nameCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_NAME));
1544 QTableWidgetItem *statusCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STATUS));
1545 QTableWidgetItem *altitudeCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ALTITUDE));
1546 QTableWidgetItem *startupCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STARTTIME));
1547 QTableWidgetItem *completionCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ENDTIME));
1548 QTableWidgetItem *captureCountCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_CAPTURES));
1551 if (!nameCell)
return;
1553 if (
nullptr != nameCell)
1555 nameCell->
setText(job->isLead() ? job->getName() :
"*");
1561 if (
nullptr != statusCell)
1564 static QString stateStringUnknown;
1575 stateStringUnknown =
i18n(
"Unknown");
1577 statusCell->
setText(stateStrings.
value(job->getState(), stateStringUnknown));
1584 if (
nullptr != startupCell)
1586 auto time = (job->getState() ==
SCHEDJOB_BUSY) ? job->getStateTime() : job->getStartupTime();
1591 .arg(job->getAltitudeAtStartup() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1592 .arg(
QChar(job->isSettingAtStartup() ? 0x2193 : 0x2191))
1593 .
arg(job->getAltitudeAtStartup(), 0,
'f', 1)
1594 .
arg(time.toString(startupTimeEdit->displayFormat())));
1595 job->setStartupFormatted(startupCell->
text());
1597 switch (job->getFileStartupCondition())
1626 if (
nullptr != altitudeCell)
1629 bool is_setting =
false;
1630 double const alt = SchedulerUtils::findAltitude(job->getTargetCoords(),
QDateTime(), &is_setting);
1633 .arg(
QChar(is_setting ? 0x2193 : 0x2191))
1634 .arg(alt, 0,
'f', 1));
1636 job->setAltitudeFormatted(altitudeCell->
text());
1642 if (
nullptr != completionCell)
1645 if (job->getStopTime().
isValid())
1648 .arg(job->getAltitudeAtStop() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1649 .arg(
QChar(job->isSettingAtStop() ? 0x2193 : 0x2191))
1650 .
arg(job->getAltitudeAtStop(), 0,
'f', 1)
1651 .
arg(job->getStopTime().
toString(startupTimeEdit->displayFormat())));
1652 job->setEndFormatted(completionCell->
text());
1654 switch (job->getCompletionCondition())
1660 case FINISH_SEQUENCE:
1679 if (
nullptr != captureCountCell)
1681 switch (job->getCompletionCondition())
1688 captureCountCell->
setText(
QString(
"%L1/-").arg(job->getCompletedCount()));
1691 case FINISH_SEQUENCE:
1695 captureCountCell->
setText(
QString(
"%L1/%L2").arg(job->getCompletedCount()).
arg(job->getSequenceCount()));
1699 QString tooltip = job->getProgressSummary();
1700 if (tooltip.
size() == 0)
1701 tooltip =
i18n(
"Count of captures stored for the job, based on its sequence job.\n"
1702 "This is a summary, additional specific frame types may be required to complete the job.");
1710 m_JobUpdateDebounce.
start();
1715 const int pos = above ? row : row + 1;
1718 if (row > queueTable->rowCount())
1721 queueTable->insertRow(
pos);
1724 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_NAME), nameCell);
1729 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STATUS), statusCell);
1734 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_CAPTURES), captureCount);
1739 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STARTTIME), startupCell);
1744 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ALTITUDE), altitudeCell);
1749 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ENDTIME), completionCell);
1762void Scheduler::resetJobEdit()
1764 if (jobUnderEdit < 0)
1767 SchedulerJob *
const job = moduleState()->jobs().at(jobUnderEdit);
1768 Q_ASSERT_X(job !=
nullptr, __FUNCTION__,
"Edited job must be valid");
1770 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is not longer edited.").
arg(job->getName()).
arg(
1780 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1783 evaluateOnlyB->setEnabled(
true);
1784 startB->setEnabled(
true);
1787 Q_ASSERT_X(jobUnderEdit == -1, __FUNCTION__,
"No more edited/selected job after exiting edit mode");
1792 int currentRow = moduleState()->currentPosition();
1795 if (moduleState()->
removeJob(currentRow) ==
false)
1803 queueTable->removeRow(currentRow);
1806 if (queueTable->rowCount() == 0)
1808 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1809 evaluateOnlyB->setEnabled(
false);
1810 queueSaveAsB->setEnabled(
false);
1811 queueSaveB->setEnabled(
false);
1812 startB->setEnabled(
false);
1813 pauseB->setEnabled(
false);
1820 queueTable->clearSelection();
1823 if (jobUnderEdit >= 0)
1827 moduleState()->refreshFollowerLists();
1828 process()->evaluateJobs(
true);
1831 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1836 moduleState()->setCurrentPosition(index);
1839void Scheduler::toggleScheduler()
1841 if (moduleState()->schedulerState() == SCHEDULER_RUNNING)
1843 moduleState()->disablePreemptiveShutdown();
1850void Scheduler::pause()
1852 moduleState()->setSchedulerState(SCHEDULER_PAUSED);
1853 process()->appendLogText(
i18n(
"Scheduler pause planned..."));
1854 pauseB->setEnabled(
false);
1857 startB->setToolTip(
i18n(
"Resume Scheduler"));
1860void Scheduler::syncGreedyParams()
1862 process()->getGreedyScheduler()->setParams(
1863 errorHandlingRestartImmediatelyButton->isChecked(),
1864 errorHandlingRestartQueueButton->isChecked(),
1865 errorHandlingRescheduleErrorsCB->isChecked(),
1866 errorHandlingStrategyDelay->value(),
1867 errorHandlingStrategyDelay->value());
1870void Scheduler::handleShutdownStarted()
1872 KSNotification::event(
QLatin1String(
"ObservatoryShutdown"),
i18n(
"Observatory is in the shutdown process"),
1873 KSNotification::Scheduler);
1874 weatherLabel->hide();
1877void Ekos::Scheduler::changeSleepLabel(
QString text,
bool show)
1879 sleepLabel->setToolTip(text);
1888 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_NOTHING).toLatin1().data());
1891 bool wasAborted =
false;
1892 for (
auto &oneJob : moduleState()->jobs())
1902 KSNotification::event(
QLatin1String(
"SchedulerAborted"),
i18n(
"Scheduler aborted."), KSNotification::Scheduler,
1903 KSNotification::Alert);
1905 startupB->setEnabled(
true);
1906 shutdownB->setEnabled(
true);
1909 if (moduleState()->preemptiveShutdown())
1911 changeSleepLabel(
i18n(
"Scheduler is in shutdown until next job is ready"));
1916 changeSleepLabel(
"",
false);
1919 startB->setToolTip(
i18n(
"Start Scheduler"));
1920 pauseB->setEnabled(
false);
1923 queueLoadB->setEnabled(
true);
1924 queueAppendB->setEnabled(
true);
1925 addToQueueB->setEnabled(
true);
1926 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1928 evaluateOnlyB->setEnabled(
true);
1934 return load(
true, path.toLocalFile());
1944 "Ekos Scheduler List (*.esl)");
1951 if (fileURL.
isValid() ==
false)
1954 KSNotification::sorry(message,
i18n(
"Invalid URL"));
1961 process()->removeAllJobs();
1963 const int row = moduleState()->jobs().count();
1968 const bool success = process()->appendEkosScheduleList(fileURL.
toLocalFile());
1975 if (moduleState()->jobs().count() > row)
1976 moduleState()->setCurrentPosition(row);
1979 process()->startJobEvaluation();
1989 if (jobUnderEdit >= 0)
1992 while (queueTable->rowCount() > 0)
1993 queueTable->removeRow(0);
1998 process()->clearLog();
2001void Scheduler::saveAs()
2003 schedulerURL.
clear();
2009 QUrl backupCurrent = schedulerURL;
2010 schedulerURL = path;
2016 schedulerURL = backupCurrent;
2021bool Scheduler::save()
2023 QUrl backupCurrent = schedulerURL;
2026 schedulerURL.
clear();
2029 if (moduleState()->dirty() ==
false && !schedulerURL.
isEmpty())
2036 "Ekos Scheduler List (*.esl)");
2040 schedulerURL = backupCurrent;
2052 if ((process()->saveScheduler(schedulerURL)) ==
false)
2054 KSNotification::error(
i18n(
"Failed to save scheduler list"),
i18n(
"Save"));
2059 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.
fileName());
2064 KSNotification::sorry(message,
i18n(
"Invalid URL"));
2071void Scheduler::checkJobInputComplete()
2074 bool const nameSelectionOK = !raBox->isEmpty() && !decBox->isEmpty() && !nameEdit->text().isEmpty();
2077 bool const fitsSelectionOK = !nameEdit->text().isEmpty() && !fitsURL.
isEmpty();
2080 bool const seqSelectionOK = !sequenceEdit->text().isEmpty();
2083 bool const addingOK = (nameSelectionOK || fitsSelectionOK) && seqSelectionOK;
2085 addToQueueB->setEnabled(addingOK);
2091 checkJobInputComplete();
2094 if (jobUnderEdit < 0)
2097 moduleState()->setDirty(
true);
2099 if (
sender() == startupProcedureButtonGroup ||
sender() == shutdownProcedureGroup)
2103 if (
sender() == schedulerStartupScript)
2105 else if (
sender() == schedulerShutdownScript)
2106 moduleState()->setShutdownScriptURL(
QUrl::fromUserInput(schedulerShutdownScript->text()));
2112 if (moduleState()->jobs().isEmpty())
2121 using namespace std::placeholders;
2123 std::stable_sort(sortedJobs.
begin() + 1, sortedJobs.
end(),
2124 std::bind(SchedulerJob::decreasingAltitudeOrder, _1, _2, moduleState()->jobs().first()->getStartupTime()));
2129 for (SchedulerJob * job : moduleState()->jobs())
2132 process()->evaluateJobs(
true);
2139 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_SCHEDULER).toLatin1().data());
2140 moduleState()->setupNextIteration(RUN_SCHEDULER);
2146 if (errorHandlingRestartQueueButton->isChecked())
2147 return ERROR_RESTART_AFTER_TERMINATION;
2148 else if (errorHandlingRestartImmediatelyButton->isChecked())
2149 return ERROR_RESTART_IMMEDIATELY;
2151 return ERROR_DONT_RESTART;
2156 errorHandlingStrategyDelay->setEnabled(strategy != ERROR_DONT_RESTART);
2160 case ERROR_RESTART_AFTER_TERMINATION:
2161 errorHandlingRestartQueueButton->setChecked(
true);
2163 case ERROR_RESTART_IMMEDIATELY:
2164 errorHandlingRestartImmediatelyButton->setChecked(
true);
2167 errorHandlingDontRestartButton->setChecked(
true);
2175void Scheduler::setAlgorithm(
int algIndex)
2177 if (algIndex != ALGORITHM_GREEDY)
2179 process()->appendLogText(
2180 i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
2181 algIndex = ALGORITHM_GREEDY;
2183 Options::setSchedulerAlgorithm(algIndex);
2185 groupLabel->setDisabled(
false);
2186 groupEdit->setDisabled(
false);
2187 queueTable->model()->setHeaderData(START_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next Start"));
2188 queueTable->model()->setHeaderData(END_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next End"));
2197 process()->appendLogText(
2198 i18n(
"Turning off astronomical twilight check may cause the observatory to run during daylight. This can cause irreversible damage to your equipment!"));
2202void Scheduler::updateProfiles()
2204 schedulerProfileCombo->blockSignals(
true);
2205 schedulerProfileCombo->clear();
2206 schedulerProfileCombo->addItems(moduleState()->profiles());
2207 schedulerProfileCombo->setCurrentText(moduleState()->currentProfile());
2208 schedulerProfileCombo->blockSignals(
false);
2211void Scheduler::updateJobStageUI(SchedulerJobStage stage)
2216 static QString stageStringUnknown;
2219 stageStrings[SCHEDSTAGE_IDLE] =
i18n(
"Idle");
2220 stageStrings[SCHEDSTAGE_SLEWING] =
i18n(
"Slewing");
2221 stageStrings[SCHEDSTAGE_SLEW_COMPLETE] =
i18n(
"Slew complete");
2222 stageStrings[SCHEDSTAGE_FOCUSING] =
2223 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING] =
i18n(
"Focusing");
2224 stageStrings[SCHEDSTAGE_FOCUS_COMPLETE] =
2225 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING_COMPLETE ] =
i18n(
"Focus complete");
2226 stageStrings[SCHEDSTAGE_ALIGNING] =
i18n(
"Aligning");
2227 stageStrings[SCHEDSTAGE_ALIGN_COMPLETE] =
i18n(
"Align complete");
2228 stageStrings[SCHEDSTAGE_RESLEWING] =
i18n(
"Repositioning");
2229 stageStrings[SCHEDSTAGE_RESLEWING_COMPLETE] =
i18n(
"Repositioning complete");
2231 stageStrings[SCHEDSTAGE_GUIDING] =
i18n(
"Guiding");
2232 stageStrings[SCHEDSTAGE_GUIDING_COMPLETE] =
i18n(
"Guiding complete");
2233 stageStrings[SCHEDSTAGE_CAPTURING] =
i18n(
"Capturing");
2234 stageStringUnknown =
i18n(
"Unknown");
2237 if (activeJob() ==
nullptr)
2238 jobStatus->setText(stageStrings[SCHEDSTAGE_IDLE]);
2240 jobStatus->setText(
QString(
"%1: %2").arg(activeJob()->getName(),
2241 stageStrings.
value(stage, stageStringUnknown)));
2247 if (iface == process()->mountInterface())
2249 QVariant canMountPark = process()->mountInterface()->property(
"canPark");
2252 schedulerUnparkMount->setEnabled(canMountPark.
toBool());
2253 schedulerParkMount->setEnabled(canMountPark.
toBool());
2256 else if (iface == process()->capInterface())
2258 QVariant canCapPark = process()->capInterface()->property(
"canPark");
2261 schedulerCloseDustCover->setEnabled(canCapPark.
toBool());
2262 schedulerOpenDustCover->setEnabled(canCapPark.
toBool());
2266 schedulerCloseDustCover->setEnabled(
false);
2267 schedulerOpenDustCover->setEnabled(
false);
2270 else if (iface == process()->weatherInterface())
2272 QVariant status = process()->weatherInterface()->property(
"status");
2273 if (status.isValid())
2278 schedulerWeather->setEnabled(
true);
2281 schedulerWeather->setEnabled(
false);
2283 else if (iface == process()->domeInterface())
2285 QVariant canDomePark = process()->domeInterface()->property(
"canPark");
2288 schedulerUnparkDome->setEnabled(canDomePark.
toBool());
2289 schedulerParkDome->setEnabled(canDomePark.
toBool());
2292 else if (iface == process()->captureInterface())
2294 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
2295 if (hasCoolerControl.
isValid())
2297 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
2302void Scheduler::setWeatherStatus(ISD::Weather::Status status)
2304 TEST_PRINT(stderr,
"sch%d @@@setWeatherStatus(%d)\n", __LINE__,
static_cast<int>(status));
2305 ISD::Weather::Status newStatus = status;
2310 case ISD::Weather::WEATHER_OK:
2311 statusString =
i18n(
"Weather conditions are OK.");
2314 case ISD::Weather::WEATHER_WARNING:
2315 statusString =
i18n(
"Warning: weather conditions are in the WARNING zone.");
2318 case ISD::Weather::WEATHER_ALERT:
2319 statusString =
i18n(
"Caution: weather conditions are in the DANGER zone!");
2326 qCDebug(KSTARS_EKOS_SCHEDULER) << statusString;
2328 if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_OK)
2329 weatherLabel->setPixmap(
2331 .pixmap(
QSize(32, 32)));
2332 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_WARNING)
2334 weatherLabel->setPixmap(
2336 .pixmap(
QSize(32, 32)));
2337 KSNotification::event(
QLatin1String(
"WeatherWarning"),
i18n(
"Weather conditions in warning zone"),
2338 KSNotification::Scheduler, KSNotification::Warn);
2340 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_ALERT)
2342 weatherLabel->setPixmap(
2344 .pixmap(
QSize(32, 32)));
2346 i18n(
"Weather conditions are critical. Observatory shutdown is imminent"), KSNotification::Scheduler,
2347 KSNotification::Alert);
2351 .pixmap(
QSize(32, 32)));
2353 weatherLabel->show();
2354 weatherLabel->setToolTip(statusString);
2356 process()->appendLogText(statusString);
2358 emit weatherChanged(moduleState()->weatherStatus());
2365 schedulerWeather->setEnabled(
false);
2366 weatherLabel->hide();
2369 changeSleepLabel(
i18n(
"Scheduler is in sleep mode"));
2376 case SCHEDULER_RUNNING:
2381 startB->setToolTip(
i18n(
"Stop Scheduler"));
2382 pauseB->setEnabled(
true);
2383 pauseB->setChecked(
false);
2386 queueLoadB->setEnabled(
false);
2387 setJobManipulation(
true,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
2389 evaluateOnlyB->setEnabled(
false);
2390 startupB->setEnabled(
false);
2391 shutdownB->setEnabled(
false);
2398 emit newStatus(newState);
2403 pauseB->setCheckable(
true);
2404 pauseB->setChecked(
true);
2407void Scheduler::handleJobsUpdated(
QJsonArray jobsList)
2412 emit jobsUpdated(jobsList);
2418 return assistant->importMosaic(payload);
2421void Scheduler::startupStateChanged(StartupState state)
2423 jobStatus->setText(startupStateString(state));
2425 switch (moduleState()->startupState())
2430 case STARTUP_COMPLETE:
2432 process()->appendLogText(
i18n(
"Manual startup procedure completed successfully."));
2436 process()->appendLogText(
i18n(
"Manual startup procedure terminated due to errors."));
2444void Scheduler::shutdownStateChanged(ShutdownState state)
2446 if (state == SHUTDOWN_COMPLETE || state == SHUTDOWN_IDLE
2447 || state == SHUTDOWN_ERROR)
2455 if (state == SHUTDOWN_IDLE)
2456 jobStatus->setText(
i18n(
"Idle"));
2458 jobStatus->setText(shutdownStateString(state));
2460void Scheduler::ekosStateChanged(EkosState state)
2462 if (state == EKOS_IDLE)
2464 jobStatus->setText(
i18n(
"Idle"));
2468 jobStatus->setText(ekosStateString(state));
2470void Scheduler::indiStateChanged(INDIState state)
2472 if (state == INDI_IDLE)
2474 jobStatus->setText(
i18n(
"Idle"));
2478 jobStatus->setText(indiStateString(state));
2480 refreshOpticalTrain();
2483void Scheduler::indiCommunicationStatusChanged(CommunicationStatus status)
2485 if (status == Success)
2486 refreshOpticalTrain();
2488void Scheduler::parkWaitStateChanged(ParkWaitState state)
2490 jobStatus->setText(parkWaitStateString(state));
2493SchedulerJob *Scheduler::activeJob()
2495 return moduleState()->activeJob();
2498void Scheduler::loadGlobalSettings()
2503 QVariantMap settings;
2507 key = oneWidget->objectName();
2508 value = Options::self()->property(key.
toLatin1());
2509 if (value.
isValid() && oneWidget->count() > 0)
2511 oneWidget->setCurrentText(value.
toString());
2512 settings[key] = value;
2515 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2521 key = oneWidget->objectName();
2522 value = Options::self()->property(key.
toLatin1());
2525 oneWidget->setValue(value.
toDouble());
2526 settings[key] = value;
2529 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2535 key = oneWidget->objectName();
2536 value = Options::self()->property(key.
toLatin1());
2539 oneWidget->setValue(value.
toInt());
2540 settings[key] = value;
2543 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2549 key = oneWidget->objectName();
2550 value = Options::self()->property(key.
toLatin1());
2553 oneWidget->setChecked(value.
toBool());
2554 settings[key] = value;
2557 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2563 key = oneWidget->objectName();
2564 value = Options::self()->property(key.
toLatin1());
2567 oneWidget->setText(value.
toString());
2568 settings[key] = value;
2570 if (key ==
"sequenceEdit")
2572 else if (key ==
"schedulerStartupScript")
2574 else if (key ==
"schedulerShutdownScript")
2578 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2584 key = oneWidget->objectName();
2585 value = Options::self()->property(key.
toLatin1());
2588 oneWidget->setChecked(value.
toBool());
2589 settings[key] = value;
2596 key = oneWidget->objectName();
2597 value = Options::self()->property(key.
toLatin1());
2601 settings[key] = value;
2607 m_GlobalSettings = m_Settings = settings;
2610void Scheduler::syncSettings()
2626 value = dsb->
value();
2632 value = sb->
value();
2644 m_Settings.remove(key);
2657 value = lineedit->
text();
2666 Options::self()->setProperty(key.
toLatin1(), value);
2668 m_Settings[key] = value;
2669 m_GlobalSettings[key] = value;
2671 emit settingsUpdated(getAllSettings());
2677QVariantMap Scheduler::getAllSettings()
const
2679 QVariantMap settings;
2683 settings.insert(oneWidget->objectName(), oneWidget->currentText());
2687 settings.insert(oneWidget->objectName(), oneWidget->value());
2691 settings.insert(oneWidget->objectName(), oneWidget->value());
2695 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2701 if (!oneWidget->objectName().startsWith(
"qt_"))
2702 settings.insert(oneWidget->objectName(), oneWidget->text());
2707 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2712 settings.insert(oneWidget->objectName(), oneWidget->dateTime().toString(
Qt::ISODate));
2721void Scheduler::setAllSettings(
const QVariantMap &settings)
2725 disconnectSettings();
2727 for (
auto &name : settings.keys())
2733 syncControl(settings, name, comboBox);
2741 syncControl(settings, name, doubleSpinBox);
2749 syncControl(settings, name, spinBox);
2757 syncControl(settings, name, checkbox);
2765 syncControl(settings, name, lineedit);
2767 if (name ==
"sequenceEdit")
2769 else if (name ==
"fitsEdit")
2771 else if (name ==
"schedulerStartupScript")
2773 else if (name ==
"schedulerShutdownScript")
2783 syncControl(settings, name, radioButton);
2790 syncControl(settings, name, datetimeedit);
2795 m_Settings = settings;
2804bool Scheduler::syncControl(
const QVariantMap &settings,
const QString &key,
QWidget * widget)
2817 const int value = settings[key].toInt(&ok);
2826 const double value = settings[key].toDouble(&ok);
2835 const bool value = settings[key].toBool();
2843 const QString value = settings[key].toString();
2849 const auto value = settings[key].toString();
2855 const bool value = settings[key].toBool();
2857 pRadioButton->
click();
2870void Scheduler::refreshOpticalTrain()
2872 opticalTrainCombo->blockSignals(
true);
2873 opticalTrainCombo->clear();
2874 opticalTrainCombo->addItem(
"--");
2875 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
2876 opticalTrainCombo->blockSignals(
false);
2879void Scheduler::connectSettings()
2905 if (!oneWidget->objectName().startsWith(
"qt_"))
2914void Scheduler::disconnectSettings()
2945void Scheduler::handleAltitudeGraph(
int index)
2947 if (!m_altitudeGraph)
2948 m_altitudeGraph =
new SchedulerAltitudeGraph;
2950 if (index < 0 || index >= moduleState()->jobs().
size())
2952 auto job = moduleState()->jobs().at(index);
2954 QDateTime now = SchedulerModuleState::getLocalTime(), start,
end;
2956 SchedulerModuleState::calculateDawnDusk(now, nextDawn, nextDusk);
2959 QDateTime plotStart = (nextDusk < nextDawn) ? nextDusk : nextDusk.addDays(-1);
2968 plotStart = plotStart.
addSecs(-1 * 3600);
2970 auto plotEnd = nextDawn.
addSecs(1 * 3600);
2971 while (t.secsTo(plotEnd) > 0)
2973 double alt = SchedulerUtils::findAltitude(job->getTargetCoords(), t);
2975 double hour = midnight.
secsTo(t) / 3600.0;
2977 t = t.addSecs(60 * 10);
2981 KSAlmanac ksal(ut, SchedulerModuleState::getGeo());
2982 m_altitudeGraph->setTitle(job->getName());
2983 m_altitudeGraph->plot(SchedulerModuleState::getGeo(), &ksal, times, alts);
2986 auto startTime = (job->getState() ==
SCHEDJOB_BUSY) ? job->getStateTime() : job->getStartupTime();
2987 if (startTime.isValid() && startTime < plotEnd && job->getStopTime().
isValid())
2989 auto stopTime = job->getStopTime();
2990 if (startTime < plotStart) startTime = plotStart;
2991 if (stopTime > plotEnd)
2996 while (t.secsTo(stopTime) > 0)
2998 double alt = SchedulerUtils::findAltitude(job->getTargetCoords(), t);
3000 double hour = midnight.
secsTo(t) / 3600.0;
3002 t = t.addSecs(60 * 10);
3005 m_altitudeGraph->plot(SchedulerModuleState::getGeo(), &ksal, runTimes, runAlts,
true);
3007 m_altitudeGraph->show();
The SchedulerProcess class holds the entire business logic for controlling the execution of the EKOS ...
Q_SCRIPTABLE Q_NOREPLY void runStartupProcedure()
runStartupProcedure Execute the startup of the scheduler itself to be prepared for running scheduler ...
Q_SCRIPTABLE Q_NOREPLY void startJobEvaluation()
startJobEvaluation Start job evaluation only without starting the scheduler process itself.
Q_SCRIPTABLE Q_NOREPLY void runShutdownProcedure()
runShutdownProcedure Shutdown the scheduler itself and EKOS (if configured to do so).
ErrorHandlingStrategy getErrorHandlingStrategy()
retrieve the error handling strategy from the UI
void moveJobUp()
moveJobUp Move the selected job up in the job list.
void watchJobChanges(bool enable)
Q_INVOKABLE void clearLog()
clearLog Clears log entry
void checkTwilightWarning(bool enabled)
checkWeather Check weather status and act accordingly depending on the current status of the schedule...
void saveJob(SchedulerJob *job=nullptr)
addToQueue Construct a SchedulerJob and add it to the queue or save job settings from current form va...
void setJobManipulation(bool can_reorder, bool can_delete, bool is_lead)
setJobManipulation Enable or disable job manipulation buttons.
void updateSchedulerURL(const QString &fileURL)
updateSchedulerURL Update scheduler URL after succesful loading a new file.
Q_INVOKABLE void addJob(SchedulerJob *job=nullptr)
addJob Add a new job from form values
void selectSequence()
Selects sequence queue.
void insertJobTableRow(int row, bool above=true)
insertJobTableRow Insert a new row (empty) into the job table
Q_INVOKABLE bool load(bool clearQueue, const QString &filename=QString())
load Open a file dialog to select an ESL file, and load its contents.
void resumeCheckStatus()
resumeCheckStatus If the scheduler primary loop was suspended due to weather or sleep event,...
void handleSchedulerSleeping(bool shutdown, bool sleep)
handleSchedulerSleeping Update UI if scheduler is set to sleep
void 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.
SkyObject * targetObject()
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)
A class that implements methods to find sun rise, sun set, twilight begin / end times,...
const KStarsDateTime & ut() const
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
static KStars * Instance()
virtual KActionCollection * actionCollection() const
The QProgressIndicator class lets an application display a progress indicator to show that a long tas...
void stopAnimation()
Stops the spin animation.
void startAnimation()
Starts the spin animation.
The SchedulerState class holds all attributes defining the scheduler's state.
Provides all necessary information about an object in the sky: its coordinates, name(s),...
virtual QString name(void) const
The sky coordinates of a point in the sky.
const CachingDms & ra0() const
const CachingDms & dec0() const
This is a subclass of SkyObject.
static dms fromString(const QString &s, bool deg)
Static function to create a DMS object from a QString.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
StartupCondition
Conditions under which a SchedulerJob may start.
@ SCHEDJOB_ABORTED
Job encountered a transitory issue while processing, and will be rescheduled.
@ SCHEDJOB_INVALID
Job has an incorrect configuration, and cannot proceed.
@ SCHEDJOB_ERROR
Job encountered a fatal issue while processing, and must be reset manually.
@ SCHEDJOB_COMPLETE
Job finished all required captures.
@ SCHEDJOB_EVALUATION
Job is being evaluated.
@ SCHEDJOB_SCHEDULED
Job was evaluated, and has a schedule.
@ SCHEDJOB_BUSY
Job is being processed.
@ SCHEDJOB_IDLE
Job was just created, and is not evaluated yet.
ErrorHandlingStrategy
options what should happen if an error or abort occurs
CompletionCondition
Conditions under which a SchedulerJob may complete.
bool isValid(QStringView ifopt)
const QList< QKeySequence > & end()
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
void clicked(const QModelIndex &index)
void doubleClicked(const QModelIndex &index)
void rangeChanged(int min, int max)
void valueChanged(int value)
void triggered(bool checked)
void activated(int index)
void currentIndexChanged(int index)
QDate addDays(qint64 ndays) const const
QDateTime addSecs(qint64 s) const const
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isValid() const const
qint64 secsTo(const QDateTime &other) const const
QString toString(QStringView format, QCalendar cal) const const
void dateTimeChanged(const QDateTime &datetime)
void valueChanged(double d)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
QUrl getOpenFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, Options options, const QStringList &supportedSchemes)
QUrl getSaveFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, Options options, const QStringList &supportedSchemes)
void setItalic(bool enable)
Qt::KeyboardModifiers keyboardModifiers()
QIcon fromTheme(const QString &name)
QModelIndexList indexes() const const
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
void textChanged(const QString &text)
void append(QList< T > &&value)
bool contains(const AT &value) const const
qsizetype count() const const
void push_back(parameter_type value)
bool isEmpty() const const
T value(const Key &key, const T &defaultValue) const const
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
T findChild(const QString &name, Qt::FindChildOptions options) const const
QList< T > findChildren(Qt::FindChildOptions options) const const
T qobject_cast(QObject *object)
QObject * sender() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
void setFont(const QFont &font)
void appendRow(QStandardItem *item)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QTextStream & center(QTextStream &stream)
void resizeColumnToContents(int column)
void setText(const QString &text)
void setTextAlignment(Qt::Alignment alignment)
QString text() const const
bool setHMS(int h, int m, int s, int ms)
void setInterval(int msec)
void setSingleShot(bool singleShot)
QString fileName(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
bool isEmpty() const const
bool isValid() const const
void setPath(const QString &path, ParsingMode mode)
QString toLocalFile() const const
QString url(FormattingOptions options) const const
bool isValid() const const
bool toBool() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QString toString() const const