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.\n"));
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);
259 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated,
this, &Scheduler::refreshOpticalTrain);
263 mosaicB->setDown(checked);
298 pauseB->setCheckable(
false);
317 connect(moduleState().data(), &SchedulerModuleState::ekosStateChanged,
this, &Scheduler::ekosStateChanged);
318 connect(moduleState().data(), &SchedulerModuleState::indiStateChanged,
this, &Scheduler::indiStateChanged);
319 connect(moduleState().data(), &SchedulerModuleState::indiCommunicationStatusChanged,
this,
320 &Scheduler::indiCommunicationStatusChanged);
322 connect(moduleState().data(), &SchedulerModuleState::startupStateChanged,
this, &Scheduler::startupStateChanged);
323 connect(moduleState().data(), &SchedulerModuleState::shutdownStateChanged,
this, &Scheduler::shutdownStateChanged);
324 connect(moduleState().data(), &SchedulerModuleState::parkWaitStateChanged,
this, &Scheduler::parkWaitStateChanged);
325 connect(moduleState().data(), &SchedulerModuleState::profilesChanged,
this, &Scheduler::updateProfiles);
327 connect(moduleState().data(), &SchedulerModuleState::jobStageChanged,
this, &Scheduler::updateJobStageUI);
329 connect(moduleState().data(), &SchedulerModuleState::currentProfileChanged,
this, [&]()
331 schedulerProfileCombo->setCurrentText(moduleState()->currentProfile());
337 connect(process().data(), &SchedulerProcess::shutdownStarted,
this, &Scheduler::handleShutdownStarted);
339 connect(process().data(), &SchedulerProcess::jobsUpdated,
this, &Scheduler::handleJobsUpdated);
340 connect(process().data(), &SchedulerProcess::targetDistance,
this, &Scheduler::targetDistance);
345 connect(process().data(), &SchedulerProcess::jobStarted,
this, &Scheduler::jobStarted);
346 connect(process().data(), &SchedulerProcess::jobEnded,
this, &Scheduler::jobEnded);
347 connect(process().data(), &SchedulerProcess::syncGreedyParams,
this, &Scheduler::syncGreedyParams);
349 connect(process().data(), &SchedulerProcess::changeSleepLabel,
this, &Scheduler::changeSleepLabel);
352 connect(process().data(), &SchedulerProcess::newWeatherStatus,
this, &Scheduler::setWeatherStatus);
360 connect(errorHandlingButtonGroup,
static_cast<void (QButtonGroup::*)(QAbstractButton *)
>
365 Options::setErrorHandlingStrategy(strategy);
366 errorHandlingStrategyDelay->setEnabled(strategy != ERROR_DONT_RESTART);
370 Options::setErrorHandlingStrategyDelay(value);
374 if (Options::schedulerAlgorithm() != ALGORITHM_GREEDY)
376 process()->appendLogText(
377 i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
378 Options::setSchedulerAlgorithm(ALGORITHM_GREEDY);
382 setAlgorithm(Options::schedulerAlgorithm());
386 SkyPoint
center = SkyMap::Instance()->getCenterPoint();
395 const SkyPoint coords = process()->mountCoords();
398 setTargetCoords(coords.
ra(), coords.
dec(),
false);
400 copyMountTargetB->setIcon(QIcon(
":/icons/ekos_mount_simple.png"));
404 if (!m_SequenceEditor)
405 m_SequenceEditor.reset(
new SequenceEditor(
this));
407 m_SequenceEditor->show();
408 m_SequenceEditor->raise();
411 m_JobUpdateDebounce.setSingleShot(
true);
412 m_JobUpdateDebounce.setInterval(1000);
415 emit jobsUpdated(moduleState()->getJSONJobs());
418 altGraph->setState(moduleState());
420 moduleState()->calculateDawnDusk();
421 process()->loadProfiles();
425 loadGlobalSettings();
427 refreshOpticalTrain();
430QString Scheduler::getCurrentJobName()
432 return (activeJob() !=
nullptr ? activeJob()->getName() :
"");
443 m_OpsOffsetSettings =
new OpsOffsetSettings();
447 m_OpsAlignmentSettings =
new OpsAlignmentSettings();
448 page = dialog->
addPage(m_OpsAlignmentSettings,
i18n(
"Alignment"));
451 m_OpsJobsSettings =
new OpsJobsSettings();
452 page = dialog->
addPage(m_OpsJobsSettings,
i18n(
"Jobs"));
455 m_OpsScriptsSettings =
new OpsScriptsSettings();
456 page = dialog->
addPage(m_OpsScriptsSettings,
i18n(
"Scripts"));
462 if (enable == jobChangesAreWatched)
474 schedulerStartupScript,
475 schedulerShutdownScript
486 schedulerProfileCombo,
488 leadFollowerSelectionCB
494 errorHandlingButtonGroup,
496 constraintButtonGroup,
497 completionButtonGroup,
498 startupProcedureButtonGroup,
499 shutdownProcedureGroup
504 errorHandlingRescheduleErrorsCB,
505 schedulerMoonSeparation,
506 schedulerMoonAltitude,
513 schedulerExecutionSequencesLimit,
514 errorHandlingStrategyDelay
519 schedulerMoonSeparationValue,
520 schedulerMoonAltitudeMaxValue,
521 schedulerAltitudeValue,
536 for (
auto *
const control : lineEdits)
541 for (
auto *
const control : dateEdits)
546 for (
auto *
const control : comboBoxes)
548 if (control == leadFollowerSelectionCB)
550 this, [
this](
int pos)
552 setJobManipulation(queueUpB->isEnabled() || queueDownB->isEnabled(), removeFromQueueB->isEnabled(),
pos == INDEX_LEAD);
561 for (
auto *
const control : buttonGroups)
562#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
570 for (
auto *
const control : buttons)
575 for (
auto *
const control : spinBoxes)
580 for (
auto *
const control : dspinBoxes)
593 for (
auto *
const control : lineEdits)
595 for (
auto *
const control : dateEdits)
597 for (
auto *
const control : comboBoxes)
599 for (
auto *
const control : buttons)
601 for (
auto *
const control : buttonGroups)
602#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
607 for (
auto *
const control : spinBoxes)
609 for (
auto *
const control : dspinBoxes)
613 jobChangesAreWatched = enable;
618 schedulerRepeatEverything->
setEnabled(Options::rememberJobProgress() ==
false);
619 executionSequenceLimit->setEnabled(Options::rememberJobProgress() ==
false);
624 if (FindDialog::Instance()->execWithParent(Ekos::Manager::Instance()) ==
QDialog::Accepted)
626 SkyObject *
object = FindDialog::Instance()->targetObject();
631void Scheduler::addObject(
SkyObject *
object)
633 if (
object !=
nullptr)
637 if (object->
name() ==
"star")
645 nameEdit->setText(finalObjectName);
646 setTargetCoords(object->
ra0(), object->
dec0());
655 "FITS (*.fits *.fit);;XISF (*.xisf)");
659 processFITSSelection(url);
662void Scheduler::processFITSSelection(
const QUrl &url)
672 const QString filename = fitsEdit->text();
674 double ra = 0, dec = 0;
676 char comment[128], error_status[512];
677 fitsfile *fptr =
nullptr;
679 if (fits_open_diskfile(&fptr, filename.
toLatin1(), READONLY, &status))
681 fits_report_error(stderr, status);
682 fits_get_errstatus(status, error_status);
688 if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status))
690 fits_report_error(stderr, status);
691 fits_get_errstatus(status, error_status);
697 char objectra_str[32] = {0};
698 if (fits_read_key(fptr, TSTRING,
"OBJCTRA", objectra_str, comment, &status))
700 if (fits_read_key(fptr, TDOUBLE,
"RA", &ra, comment, &status))
702 fits_report_error(stderr, status);
703 fits_get_errstatus(status, error_status);
704 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTRA (%1).", QString(error_status)));
716 char objectde_str[32] = {0};
717 if (fits_read_key(fptr, TSTRING,
"OBJCTDEC", objectde_str, comment, &status))
719 if (fits_read_key(fptr, TDOUBLE,
"DEC", &dec, comment, &status))
721 fits_report_error(stderr, status);
722 fits_get_errstatus(status, error_status);
723 process()->appendLogText(
i18n(
"FITS header: cannot find OBJCTDEC (%1).", QString(error_status)));
734 setTargetCoords(raDMS, deDMS);
736 char object_str[256] = {0};
737 if (fits_read_key(fptr, TSTRING,
"OBJECT", object_str, comment, &status))
739 QFileInfo info(filename);
740 nameEdit->setText(info.completeBaseName());
744 nameEdit->setText(object_str);
748bool Scheduler::processCoordinates(dms &ra, dms &dec)
751 bool raOk =
false, decOk =
false;
752 ra = raBox->createDms(&raOk);
753 dec = decBox->createDms(&decOk);
756 process()->appendLogText(
i18n(
"Warning: RA value %1 is invalid.", raBox->text()));
759 process()->appendLogText(
i18n(
"Warning: DEC value %1 is invalid.", decBox->text()));
762 return (raOk && decOk);
773 sequenceEdit->setText(sequenceURL.toLocalFile());
781 dirPath.toLocalFile(),
782 i18n(
"Ekos Sequence Queue (*.esq)"));
790 "Select Startup Script"),
792 i18n(
"Script (*)")));
793 if (moduleState()->startupScriptURL().isEmpty())
798 moduleState()->setDirty(
true);
799 schedulerStartupScript->setText(moduleState()->startupScriptURL().toLocalFile());
805 "Select Shutdown Script"),
807 i18n(
"Script (*)")));
808 if (moduleState()->shutdownScriptURL().isEmpty())
813 moduleState()->setDirty(
true);
814 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toLocalFile());
819 if (0 <= jobUnderEdit)
822 job = moduleState()->jobs().at(jobUnderEdit);
832 int currentRow = moduleState()->currentPosition();
836 currentRow = queueTable->rowCount();
844 if (moduleState()->jobs().count() > currentRow)
845 moduleState()->setCurrentPosition(currentRow);
848 emit jobsUpdated(moduleState()->getJSONJobs());
855 auto job = moduleState()->jobs().at(index);
862 emit jobsUpdated(moduleState()->getJSONJobs());
869 if (nameEdit->text().isEmpty())
871 process()->appendLogText(
i18n(
"Warning: Target name is required."));
875 if (sequenceEdit->text().isEmpty())
877 process()->appendLogText(
i18n(
"Warning: Sequence file is required."));
882 if ((raBox->isEmpty() || decBox->isEmpty()) && fitsURL.isEmpty())
884 process()->appendLogText(
i18n(
"Warning: Target coordinates are required."));
889 if (readCoordsFromUI() ==
false)
898 if (asapConditionR->isChecked())
899 startCondition = START_ASAP;
902 if (schedulerCompleteSequences->isChecked())
903 stopCondition = FINISH_SEQUENCE;
904 else if (schedulerRepeatSequences->isChecked())
905 stopCondition = FINISH_REPEAT;
906 else if (schedulerUntilTerminated->isChecked())
907 stopCondition = FINISH_LOOP;
909 double altConstraint = SchedulerJob::UNDEFINED_ALTITUDE;
910 if (schedulerAltitude->isChecked())
911 altConstraint = schedulerAltitudeValue->value();
913 double moonSeparation = -1;
914 if (schedulerMoonSeparation->isChecked())
915 moonSeparation = schedulerMoonSeparationValue->value();
917 double moonMaxAltitude = 90;
918 if (schedulerMoonAltitude->isChecked())
919 moonMaxAltitude = schedulerMoonAltitudeMaxValue->value();
921 QString train = opticalTrainCombo->currentText() ==
"--" ?
"" : opticalTrainCombo->currentText();
925 SchedulerUtils::setupJob(*job, nameEdit->text(), leadFollowerSelectionCB->currentIndex() == INDEX_LEAD, groupEdit->text(),
926 train, targetCoords.ra0(), targetCoords.dec0(),
927 KStarsData::Instance()->ut().djd(),
928 positionAngleSpin->value(), sequenceURL, fitsURL,
930 startCondition, startupTimeEdit->dateTime(),
931 stopCondition, schedulerUntilValue->dateTime(), schedulerExecutionSequencesLimit->value(),
936 schedulerTwilight->isChecked(),
937 schedulerHorizon->isChecked(),
939 schedulerTrackStep->isChecked(),
940 schedulerFocusStep->isChecked(),
941 schedulerAlignStep->isChecked(),
942 schedulerGuideStep->isChecked());
949bool Scheduler::readCoordsFromUI()
952 if (processCoordinates(ra, dec) ==
false)
960 setTargetCoords(ra, dec, epochCB->currentText() ==
"J2000");
969 int currentRow = moduleState()->currentPosition() + 1;
974 if (0 <= jobUnderEdit)
977 if (jobUnderEdit != currentRow - 1)
979 qCWarning(KSTARS_EKOS_SCHEDULER) <<
"BUG: the observation job under edit does not match the selected row in the job table.";
983 job = moduleState()->jobs().at(jobUnderEdit);
996 job =
new SchedulerJob();
1006 moduleState()->mutlableJobs().insert(currentRow, job);
1012 job->setLeadJob(moduleState()->findLead(currentRow - 1));
1013 moduleState()->refreshFollowerLists();
1018 int numWarnings = 0;
1021 foreach (SchedulerJob *a_job, moduleState()->jobs())
1023 if (a_job == job || !a_job->isLead())
1027 else if (a_job->getName() == job->getName())
1029 int const a_job_row = moduleState()->jobs().indexOf(a_job);
1032 process()->appendLogText(
i18n(
"Warning: job '%1' at row %2 has a duplicate target at row %3, "
1033 "the scheduler may consider the same storage for captures.",
1034 job->getName(), currentRow, a_job_row));
1037 if (a_job->getSequenceFile() == job->getSequenceFile())
1039 if (a_job->getRepeatsRequired() == job->getRepeatsRequired() && Options::rememberJobProgress())
1040 process()->appendLogText(
i18n(
"Warning: jobs '%1' at row %2 and %3 probably require a different repeat count "
1041 "as currently they will complete simultaneously after %4 batches (or disable option 'Remember job progress')",
1042 job->getName(), currentRow, a_job_row, job->getRepeatsRequired()));
1046 if (++numWarnings >= 1)
1048 process()->appendLogText(
i18n(
"Skipped checking for duplicates."));
1058 queueSaveAsB->setEnabled(
true);
1059 queueSaveB->setEnabled(
true);
1060 startB->setEnabled(
true);
1061 evaluateOnlyB->setEnabled(
true);
1063 checkJobInputComplete();
1065 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 was saved.").
arg(job->getName()).
arg(currentRow + 1);
1069 if (SCHEDULER_LOADING != moduleState()->schedulerState())
1071 process()->evaluateJobs(
true);
1077 nameEdit->setText(job->getName());
1078 groupEdit->setText(job->getGroup());
1080 setTargetCoords(job->getTargetCoords().
ra0(), job->getTargetCoords().
dec0());
1083 fitsURL = job->getFITSFile().
isEmpty() ?
QUrl() : job->getFITSFile();
1084 fitsEdit->setText(fitsURL.toLocalFile());
1086 schedulerTrackStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_TRACK);
1087 schedulerFocusStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_FOCUS);
1088 schedulerAlignStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_ALIGN);
1089 schedulerGuideStep->setChecked(job->getStepPipeline() & SchedulerJob::USE_GUIDE);
1091 switch (job->getFileStartupCondition())
1094 asapConditionR->setChecked(
true);
1098 startupTimeConditionR->setChecked(
true);
1099 startupTimeEdit->setDateTime(job->getStartupTime());
1103 if (job->getMinAltitude())
1105 schedulerAltitude->setChecked(
true);
1106 schedulerAltitudeValue->setValue(job->getMinAltitude());
1110 schedulerAltitude->setChecked(
false);
1111 schedulerAltitudeValue->setValue(DEFAULT_MIN_ALTITUDE);
1114 if (job->getMinMoonSeparation() > 0)
1116 schedulerMoonSeparation->setChecked(
true);
1117 schedulerMoonSeparationValue->setValue(job->getMinMoonSeparation());
1121 schedulerMoonSeparation->setChecked(
false);
1122 schedulerMoonSeparationValue->setValue(DEFAULT_MIN_MOON_SEPARATION);
1125 if (job->getMaxMoonAltitude() < 90)
1127 schedulerMoonAltitude->setChecked(
true);
1128 schedulerMoonAltitudeMaxValue->setValue(job->getMaxMoonAltitude());
1132 schedulerMoonAltitude->setChecked(
false);
1135 schedulerTwilight->blockSignals(
true);
1136 schedulerTwilight->setChecked(job->getEnforceTwilight());
1137 schedulerTwilight->blockSignals(
false);
1139 schedulerHorizon->blockSignals(
true);
1140 schedulerHorizon->setChecked(job->getEnforceArtificialHorizon());
1141 schedulerHorizon->blockSignals(
false);
1145 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1149 leadFollowerSelectionCB->setCurrentIndex(INDEX_FOLLOWER);
1152 if (job->getOpticalTrain().
isEmpty())
1153 opticalTrainCombo->setCurrentIndex(0);
1155 opticalTrainCombo->setCurrentText(job->getOpticalTrain());
1157 sequenceURL = job->getSequenceFile();
1158 sequenceEdit->setText(sequenceURL.toLocalFile());
1160 positionAngleSpin->setValue(job->getPositionAngle());
1162 switch (job->getCompletionCondition())
1164 case FINISH_SEQUENCE:
1165 schedulerCompleteSequences->setChecked(
true);
1169 schedulerRepeatSequences->setChecked(
true);
1170 schedulerExecutionSequencesLimit->setValue(job->getRepeatsRequired());
1174 schedulerUntilTerminated->setChecked(
true);
1178 schedulerUntil->setChecked(
true);
1179 schedulerUntilValue->setDateTime(job->getFinishAtTime());
1189 schedulerParkDome->setChecked(Options::schedulerParkDome());
1190 schedulerParkMount->setChecked(Options::schedulerParkMount());
1191 schedulerCloseDustCover->setChecked(Options::schedulerCloseDustCover());
1192 schedulerWarmCCD->setChecked(Options::schedulerWarmCCD());
1193 schedulerUnparkDome->setChecked(Options::schedulerUnparkDome());
1194 schedulerUnparkMount->setChecked(Options::schedulerUnparkMount());
1195 schedulerOpenDustCover->setChecked(Options::schedulerOpenDustCover());
1197 errorHandlingStrategyDelay->setValue(Options::errorHandlingStrategyDelay());
1198 errorHandlingRescheduleErrorsCB->setChecked(Options::rescheduleErrors());
1200 schedulerShutdownScript->setText(moduleState()->shutdownScriptURL().toString(
QUrl::PreferLocalFile));
1202 if (process()->captureInterface() !=
nullptr)
1204 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
1205 if (hasCoolerControl.
isValid())
1207 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
1208 moduleState()->setCaptureReady(
true);
1216 if (job ==
nullptr && moduleState()->jobs().
size() > 0)
1218 int const currentRow = moduleState()->currentPosition();
1219 if (0 <= currentRow && currentRow < moduleState()->jobs().
size())
1220 job = moduleState()->jobs().at(currentRow);
1224 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Cannot update night time, no matching job found at line" << currentRow;
1229 QDateTime const dawn = job ? job->getDawnAstronomicalTwilight() : moduleState()->Dawn();
1230 QDateTime const dusk = job ? job->getDuskAstronomicalTwilight() : moduleState()->Dusk();
1232 QChar const warning(dawn == dusk ? 0x26A0 :
'-');
1236bool Scheduler::modifyJob(
int index)
1244 queueTable->selectRow(index);
1245 auto modelIndex = queueTable->model()->index(index, 0);
1252 if (jobUnderEdit == i.
row())
1255 SchedulerJob *
const job = moduleState()->jobs().at(i.
row());
1270 startB->setEnabled(
false);
1271 evaluateOnlyB->setEnabled(
false);
1276 jobUnderEdit = i.
row();
1277 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is currently edited.").
arg(job->getName()).
arg(
1287 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.fileName());
1292 Q_UNUSED(deselected)
1295 if (jobChangesAreWatched ==
false || selected.
empty())
1301 if ((current.
row() + 1) > moduleState()->jobs().
size())
1303 qCWarning(KSTARS_EKOS_SCHEDULER()) <<
"Unexpected row number" << current.
row() <<
"- ignoring.";
1306 moduleState()->setCurrentPosition(current.
row());
1307 SchedulerJob *
const job = moduleState()->jobs().at(current.
row());
1311 if (jobUnderEdit < 0)
1313 else if (jobUnderEdit != current.
row())
1316 process()->appendLogText(
i18n(
"Stop editing of job #%1, resetting to original value.", jobUnderEdit + 1));
1321 else nightTime->setText(
"-");
1327 if (index.
isValid() && index.
row() < moduleState()->jobs().count())
1338 addToQueueB->setToolTip(
i18n(
"Use edition fields to create a new job in the observation list."));
1344 addToQueueB->setToolTip(
i18n(
"Apply job changes."));
1347 checkJobInputComplete();
1354 int const currentRow = moduleState()->currentPosition();
1355 if (currentRow >= 0)
1357 SchedulerJob *currentJob = moduleState()->jobs().at(currentRow);
1359 queueUpB->setEnabled(0 < currentRow &&
1360 (currentJob->isLead() || (currentRow > 1 && moduleState()->findLead(currentRow - 2) !=
nullptr)));
1362 queueDownB->setEnabled(currentRow < queueTable->rowCount() - 1 &&
1363 (moduleState()->findLead(currentRow + 1,
false) !=
nullptr));
1368 queueUpB->setEnabled(
false);
1369 queueDownB->setEnabled(
false);
1371 sortJobsB->setEnabled(can_reorder);
1372 removeFromQueueB->setEnabled(can_delete);
1374 nameEdit->setEnabled(is_lead);
1375 selectObjectB->setEnabled(is_lead);
1376 targetStarLabel->setVisible(is_lead);
1377 raBox->setEnabled(is_lead);
1378 decBox->setEnabled(is_lead);
1379 copySkyCenterB->setEnabled(is_lead);
1380 schedulerProfileCombo->setEnabled(is_lead);
1381 fitsEdit->setEnabled(is_lead);
1382 selectFITSB->setEnabled(is_lead);
1383 groupEdit->setEnabled(is_lead);
1384 schedulerTrackStep->setEnabled(is_lead);
1385 schedulerFocusStep->setEnabled(is_lead);
1386 schedulerAlignStep->setEnabled(is_lead);
1387 schedulerGuideStep->setEnabled(is_lead);
1388 startupGroup->setEnabled(is_lead);
1389 contraintsGroup->setEnabled(is_lead);
1392 leadFollowerSelectionCB->setEnabled(moduleState()->findLead(queueTable->currentRow()) !=
nullptr);
1393 if (leadFollowerSelectionCB->isEnabled() ==
false)
1394 leadFollowerSelectionCB->setCurrentIndex(INDEX_LEAD);
1400 foreach (SchedulerJob* job, moduleState()->jobs())
1401 if (!reordered_sublist.
contains(job))
1402 reordered_sublist.
append(job);
1404 if (moduleState()->jobs() != reordered_sublist)
1407 int const selectedRow = moduleState()->currentPosition();
1408 SchedulerJob *
const selectedJob = 0 <= selectedRow ? moduleState()->jobs().at(selectedRow) :
nullptr;
1411 moduleState()->setJobs(reordered_sublist);
1414 for (SchedulerJob *job : moduleState()->jobs())
1418 if (
nullptr != selectedJob)
1419 moduleState()->setCurrentPosition(moduleState()->jobs().indexOf(selectedJob));
1428 int const rowCount = queueTable->rowCount();
1429 int const currentRow = queueTable->currentRow();
1431 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1433 if (moduleState()->jobs().at(currentRow)->isLead())
1435 int const rows = 1 + job->followerJobs().
count();
1437 if (currentRow - rows < 0)
1441 destinationRow = currentRow - 1 - moduleState()->jobs().at(currentRow - rows)->followerJobs().count();
1444 destinationRow = currentRow - 1;
1447 if (currentRow < 0 || rowCount <= 1 || destinationRow < 0)
1450 if (moduleState()->jobs().at(currentRow)->isLead())
1453 moduleState()->mutlableJobs().removeOne(job);
1454 for (
auto follower : job->followerJobs())
1455 moduleState()->mutlableJobs().removeOne(follower);
1458 moduleState()->mutlableJobs().insert(destinationRow++, job);
1460 for (
auto follower : job->followerJobs())
1461 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1463 for (
int i = currentRow; i > destinationRow; i--)
1466 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1471#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1472 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1474 moduleState()->jobs().swap(currentRow, destinationRow);
1482 moduleState()->setCurrentPosition(destinationRow);
1484 SchedulerJob *newLead = moduleState()->findLead(destinationRow,
true);
1485 if (newLead !=
nullptr)
1487 job->setLeadJob(newLead);
1488 moduleState()->refreshFollowerLists();
1492 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1495 moduleState()->setDirty(
true);
1496 process()->evaluateJobs(
true);
1501 int const rowCount = queueTable->rowCount();
1502 int const currentRow = queueTable->currentRow();
1504 SchedulerJob *job = moduleState()->jobs().at(currentRow);
1506 if (moduleState()->jobs().at(currentRow)->isLead())
1508 int const rows = 1 + job->followerJobs().
count();
1510 if (currentRow + rows >= moduleState()->jobs().count())
1514 destinationRow = currentRow + 1 + moduleState()->jobs().at(currentRow + rows)->followerJobs().count();
1517 destinationRow = currentRow + 1;
1520 if (currentRow < 0 || rowCount <= 1 || destinationRow >= rowCount)
1523 if (moduleState()->jobs().at(currentRow)->isLead())
1526 moduleState()->mutlableJobs().removeOne(job);
1527 for (
auto follower : job->followerJobs())
1528 moduleState()->mutlableJobs().removeOne(follower);
1531 moduleState()->mutlableJobs().insert(destinationRow++, job);
1533 for (
auto follower : job->followerJobs())
1534 moduleState()->mutlableJobs().insert(destinationRow++, follower);
1536 for (
int i = currentRow; i < destinationRow; i++)
1539 moduleState()->setCurrentPosition(destinationRow - job->followerJobs().
count() - 1);
1544#if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
1545 moduleState()->mutlableJobs().swapItemsAt(currentRow, destinationRow);
1547 moduleState()->mutlableJobs().swap(currentRow, destinationRow);
1553 moduleState()->setCurrentPosition(destinationRow);
1555 if (moduleState()->jobs().at(currentRow)->isLead())
1557 job->setLeadJob(moduleState()->jobs().at(currentRow));
1558 moduleState()->refreshFollowerLists();
1562 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1565 moduleState()->setDirty(
true);
1566 process()->evaluateJobs(
true);
1574 for (
auto onejob : moduleState()->jobs())
1580 const int row = moduleState()->jobs().indexOf(job);
1585 if (row >= queueTable->rowCount())
1588 QTableWidgetItem *nameCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_NAME));
1589 QTableWidgetItem *statusCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STATUS));
1590 QTableWidgetItem *altitudeCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ALTITUDE));
1591 QTableWidgetItem *startupCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_STARTTIME));
1592 QTableWidgetItem *completionCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_ENDTIME));
1593 QTableWidgetItem *captureCountCell = queueTable->item(row,
static_cast<int>(SCHEDCOL_CAPTURES));
1596 if (!nameCell)
return;
1598 if (
nullptr != nameCell)
1600 nameCell->
setText(job->isLead() ? job->getName() :
"*");
1606 if (
nullptr != statusCell)
1609 static QString stateStringUnknown;
1620 stateStringUnknown =
i18n(
"Unknown");
1622 statusCell->
setText(stateStrings.
value(job->getState(), stateStringUnknown));
1629 if (
nullptr != startupCell)
1631 auto time = (job->getState() ==
SCHEDJOB_BUSY) ? job->getStateTime() : job->getStartupTime();
1636 .arg(job->getAltitudeAtStartup() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1637 .arg(
QChar(job->isSettingAtStartup() ? 0x2193 : 0x2191))
1638 .
arg(job->getAltitudeAtStartup(), 0,
'f', 1)
1639 .
arg(time.toString(startupTimeEdit->displayFormat())));
1640 job->setStartupFormatted(startupCell->
text());
1642 switch (job->getFileStartupCondition())
1671 if (
nullptr != altitudeCell)
1674 bool is_setting =
false;
1675 double const alt = SchedulerUtils::findAltitude(job->getTargetCoords(),
QDateTime(), &is_setting);
1678 .arg(
QChar(is_setting ? 0x2193 : 0x2191))
1679 .arg(alt, 0,
'f', 1));
1681 job->setAltitudeFormatted(altitudeCell->
text());
1687 if (
nullptr != completionCell)
1690 if (job->getStopTime().
isValid())
1693 .arg(job->getAltitudeAtStop() < job->getMinAltitude() ?
QString(
QChar(0x26A0)) :
"")
1694 .arg(
QChar(job->isSettingAtStop() ? 0x2193 : 0x2191))
1695 .
arg(job->getAltitudeAtStop(), 0,
'f', 1)
1696 .
arg(job->getStopTime().
toString(startupTimeEdit->displayFormat())));
1697 job->setEndFormatted(completionCell->
text());
1699 switch (job->getCompletionCondition())
1705 case FINISH_SEQUENCE:
1724 if (
nullptr != captureCountCell)
1726 switch (job->getCompletionCondition())
1733 captureCountCell->
setText(
QString(
"%L1/-").arg(job->getCompletedCount()));
1736 case FINISH_SEQUENCE:
1740 captureCountCell->
setText(
QString(
"%L1/%L2").arg(job->getCompletedCount()).
arg(job->getSequenceCount()));
1744 QString tooltip = job->getProgressSummary();
1745 if (tooltip.
size() == 0)
1746 tooltip =
i18n(
"Count of captures stored for the job, based on its sequence job.\n"
1747 "This is a summary, additional specific frame types may be required to complete the job.");
1755 m_JobUpdateDebounce.start();
1760 const int pos = above ? row : row + 1;
1763 if (row > queueTable->rowCount())
1766 queueTable->insertRow(
pos);
1769 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_NAME), nameCell);
1774 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STATUS), statusCell);
1779 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_CAPTURES), captureCount);
1784 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_STARTTIME), startupCell);
1789 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ALTITUDE), altitudeCell);
1794 queueTable->setItem(row,
static_cast<int>(SCHEDCOL_ENDTIME), completionCell);
1807void Scheduler::resetJobEdit()
1809 if (jobUnderEdit < 0)
1812 SchedulerJob *
const job = moduleState()->jobs().at(jobUnderEdit);
1813 Q_ASSERT_X(job !=
nullptr, __FUNCTION__,
"Edited job must be valid");
1815 qCDebug(KSTARS_EKOS_SCHEDULER) <<
QString(
"Job '%1' at row #%2 is not longer edited.").
arg(job->getName()).
arg(
1825 setJobManipulation(
true,
true, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1828 evaluateOnlyB->setEnabled(
true);
1829 startB->setEnabled(
true);
1832 Q_ASSERT_X(jobUnderEdit == -1, __FUNCTION__,
"No more edited/selected job after exiting edit mode");
1837 int currentRow = moduleState()->currentPosition();
1840 if (moduleState()->
removeJob(currentRow) ==
false)
1848 queueTable->removeRow(currentRow);
1851 if (queueTable->rowCount() == 0)
1853 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1854 evaluateOnlyB->setEnabled(
false);
1855 queueSaveAsB->setEnabled(
false);
1856 queueSaveB->setEnabled(
false);
1857 startB->setEnabled(
false);
1858 pauseB->setEnabled(
false);
1865 queueTable->clearSelection();
1868 if (jobUnderEdit >= 0)
1872 moduleState()->refreshFollowerLists();
1873 process()->evaluateJobs(
true);
1876 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1881 moduleState()->setCurrentPosition(index);
1884void Scheduler::toggleScheduler()
1886 if (moduleState()->schedulerState() == SCHEDULER_RUNNING)
1888 moduleState()->disablePreemptiveShutdown();
1895void Scheduler::pause()
1897 moduleState()->setSchedulerState(SCHEDULER_PAUSED);
1898 process()->appendLogText(
i18n(
"Scheduler pause planned..."));
1899 pauseB->setEnabled(
false);
1902 startB->setToolTip(
i18n(
"Resume Scheduler"));
1905void Scheduler::syncGreedyParams()
1907 process()->getGreedyScheduler()->setParams(
1908 errorHandlingRestartImmediatelyButton->isChecked(),
1909 errorHandlingRestartQueueButton->isChecked(),
1910 errorHandlingRescheduleErrorsCB->isChecked(),
1911 errorHandlingStrategyDelay->value(),
1912 errorHandlingStrategyDelay->value());
1915void Scheduler::handleShutdownStarted()
1917 KSNotification::event(QLatin1String(
"ObservatoryShutdown"),
i18n(
"Observatory is in the shutdown process"),
1918 KSNotification::Scheduler);
1919 weatherLabel->hide();
1922void Ekos::Scheduler::changeSleepLabel(QString text,
bool show)
1924 sleepLabel->setToolTip(text);
1933 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_NOTHING).toLatin1().data());
1936 bool wasAborted =
false;
1937 for (
auto &oneJob : moduleState()->jobs())
1947 KSNotification::event(
QLatin1String(
"SchedulerAborted"),
i18n(
"Scheduler aborted."), KSNotification::Scheduler,
1948 KSNotification::Alert);
1950 startupB->setEnabled(
true);
1951 shutdownB->setEnabled(
true);
1954 if (moduleState()->preemptiveShutdown())
1956 changeSleepLabel(
i18n(
"Scheduler is in shutdown until next job is ready"));
1957 pi->stopAnimation();
1961 changeSleepLabel(
"",
false);
1964 startB->setToolTip(
i18n(
"Start Scheduler"));
1965 pauseB->setEnabled(
false);
1968 queueLoadB->setEnabled(
true);
1969 queueAppendB->setEnabled(
true);
1970 addToQueueB->setEnabled(
true);
1971 setJobManipulation(
false,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
1973 evaluateOnlyB->setEnabled(
true);
1979 return load(
true, path.toLocalFile());
1989 "Ekos Scheduler List (*.esl)");
1996 if (fileURL.
isValid() ==
false)
1999 KSNotification::sorry(message,
i18n(
"Invalid URL"));
2006 process()->removeAllJobs();
2008 const int row = moduleState()->jobs().count();
2013 const bool success = process()->appendEkosScheduleList(fileURL.
toLocalFile());
2020 if (moduleState()->jobs().count() > row)
2021 moduleState()->setCurrentPosition(row);
2024 process()->startJobEvaluation();
2034 if (jobUnderEdit >= 0)
2037 while (queueTable->rowCount() > 0)
2038 queueTable->removeRow(0);
2043 process()->clearLog();
2046void Scheduler::saveAs()
2048 schedulerURL.
clear();
2054 QUrl backupCurrent = schedulerURL;
2055 schedulerURL = path;
2061 schedulerURL = backupCurrent;
2066bool Scheduler::save()
2068 QUrl backupCurrent = schedulerURL;
2071 schedulerURL.
clear();
2074 if (moduleState()->dirty() ==
false && !schedulerURL.
isEmpty())
2081 "Ekos Scheduler List (*.esl)");
2085 schedulerURL = backupCurrent;
2095 if (schedulerURL.isValid())
2097 if ((process()->saveScheduler(schedulerURL)) ==
false)
2099 KSNotification::error(
i18n(
"Failed to save scheduler list"),
i18n(
"Save"));
2104 queueSaveB->setToolTip(
"Save schedule to " + schedulerURL.fileName());
2108 QString message =
i18n(
"Invalid URL: %1", schedulerURL.url());
2109 KSNotification::sorry(message,
i18n(
"Invalid URL"));
2116void Scheduler::checkJobInputComplete()
2119 bool const nameSelectionOK = !raBox->isEmpty() && !decBox->isEmpty() && !nameEdit->text().isEmpty();
2122 bool const fitsSelectionOK = !nameEdit->text().isEmpty() && !fitsURL.isEmpty();
2125 bool const seqSelectionOK = !sequenceEdit->text().isEmpty();
2128 bool const addingOK = (nameSelectionOK || fitsSelectionOK) && seqSelectionOK;
2130 addToQueueB->setEnabled(addingOK);
2136 checkJobInputComplete();
2139 if (jobUnderEdit < 0)
2142 moduleState()->setDirty(
true);
2144 if (
sender() == startupProcedureButtonGroup ||
sender() == shutdownProcedureGroup)
2148 if (
sender() == schedulerStartupScript)
2150 else if (
sender() == schedulerShutdownScript)
2151 moduleState()->setShutdownScriptURL(
QUrl::fromUserInput(schedulerShutdownScript->text()));
2157 if (moduleState()->jobs().isEmpty())
2166 using namespace std::placeholders;
2168 std::stable_sort(sortedJobs.
begin() + 1, sortedJobs.
end(),
2169 std::bind(SchedulerJob::decreasingAltitudeOrder, _1, _2, moduleState()->jobs().first()->getStartupTime()));
2174 for (SchedulerJob * job : moduleState()->jobs())
2177 process()->evaluateJobs(
true);
2184 TEST_PRINT(stderr,
"%d Setting %s\n", __LINE__, timerStr(RUN_SCHEDULER).toLatin1().data());
2185 moduleState()->setupNextIteration(RUN_SCHEDULER);
2191 if (errorHandlingRestartQueueButton->isChecked())
2192 return ERROR_RESTART_AFTER_TERMINATION;
2193 else if (errorHandlingRestartImmediatelyButton->isChecked())
2194 return ERROR_RESTART_IMMEDIATELY;
2196 return ERROR_DONT_RESTART;
2201 errorHandlingStrategyDelay->setEnabled(strategy != ERROR_DONT_RESTART);
2205 case ERROR_RESTART_AFTER_TERMINATION:
2206 errorHandlingRestartQueueButton->setChecked(
true);
2208 case ERROR_RESTART_IMMEDIATELY:
2209 errorHandlingRestartImmediatelyButton->setChecked(
true);
2212 errorHandlingDontRestartButton->setChecked(
true);
2220void Scheduler::setAlgorithm(
int algIndex)
2222 if (algIndex != ALGORITHM_GREEDY)
2224 process()->appendLogText(
2225 i18n(
"Warning: The Classic scheduler algorithm has been retired. Switching you to the Greedy algorithm."));
2226 algIndex = ALGORITHM_GREEDY;
2228 Options::setSchedulerAlgorithm(algIndex);
2230 groupLabel->setDisabled(
false);
2231 groupEdit->setDisabled(
false);
2232 queueTable->model()->setHeaderData(START_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next Start"));
2233 queueTable->model()->setHeaderData(END_TIME_COLUMN,
Qt::Horizontal,
tr(
"Next End"));
2242 process()->appendLogText(
2243 i18n(
"Turning off astronomical twilight check may cause the observatory to run during daylight. This can cause irreversible damage to your equipment!"));
2247void Scheduler::updateProfiles()
2249 schedulerProfileCombo->blockSignals(
true);
2250 schedulerProfileCombo->clear();
2251 schedulerProfileCombo->addItems(moduleState()->profiles());
2252 schedulerProfileCombo->setCurrentText(moduleState()->currentProfile());
2253 schedulerProfileCombo->blockSignals(
false);
2256void Scheduler::updateJobStageUI(SchedulerJobStage stage)
2261 static QString stageStringUnknown;
2264 stageStrings[SCHEDSTAGE_IDLE] =
i18n(
"Idle");
2265 stageStrings[SCHEDSTAGE_SLEWING] =
i18n(
"Slewing");
2266 stageStrings[SCHEDSTAGE_SLEW_COMPLETE] =
i18n(
"Slew complete");
2267 stageStrings[SCHEDSTAGE_FOCUSING] =
2268 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING] =
i18n(
"Focusing");
2269 stageStrings[SCHEDSTAGE_FOCUS_COMPLETE] =
2270 stageStrings[SCHEDSTAGE_POSTALIGN_FOCUSING_COMPLETE ] =
i18n(
"Focus complete");
2271 stageStrings[SCHEDSTAGE_ALIGNING] =
i18n(
"Aligning");
2272 stageStrings[SCHEDSTAGE_ALIGN_COMPLETE] =
i18n(
"Align complete");
2273 stageStrings[SCHEDSTAGE_RESLEWING] =
i18n(
"Repositioning");
2274 stageStrings[SCHEDSTAGE_RESLEWING_COMPLETE] =
i18n(
"Repositioning complete");
2276 stageStrings[SCHEDSTAGE_GUIDING] =
i18n(
"Guiding");
2277 stageStrings[SCHEDSTAGE_GUIDING_COMPLETE] =
i18n(
"Guiding complete");
2278 stageStrings[SCHEDSTAGE_CAPTURING] =
i18n(
"Capturing");
2279 stageStringUnknown =
i18n(
"Unknown");
2282 if (activeJob() ==
nullptr)
2283 jobStatus->setText(stageStrings[SCHEDSTAGE_IDLE]);
2285 jobStatus->setText(QString(
"%1: %2").arg(activeJob()->getName(),
2286 stageStrings.
value(stage, stageStringUnknown)));
2292 if (iface == process()->mountInterface())
2294 QVariant canMountPark = process()->mountInterface()->property(
"canPark");
2297 schedulerUnparkMount->setEnabled(canMountPark.
toBool());
2298 schedulerParkMount->setEnabled(canMountPark.
toBool());
2300 copyMountTargetB->setEnabled(
true);
2302 else if (iface == process()->capInterface())
2304 QVariant canCapPark = process()->capInterface()->property(
"canPark");
2307 schedulerCloseDustCover->setEnabled(canCapPark.
toBool());
2308 schedulerOpenDustCover->setEnabled(canCapPark.
toBool());
2312 schedulerCloseDustCover->setEnabled(
false);
2313 schedulerOpenDustCover->setEnabled(
false);
2316 else if (iface == process()->domeInterface())
2318 QVariant canDomePark = process()->domeInterface()->property(
"canPark");
2321 schedulerUnparkDome->setEnabled(canDomePark.
toBool());
2322 schedulerParkDome->setEnabled(canDomePark.
toBool());
2325 else if (iface == process()->captureInterface())
2327 QVariant hasCoolerControl = process()->captureInterface()->property(
"coolerControl");
2328 if (hasCoolerControl.
isValid())
2330 schedulerWarmCCD->setEnabled(hasCoolerControl.
toBool());
2335void Scheduler::setWeatherStatus(ISD::Weather::Status status)
2337 TEST_PRINT(stderr,
"sch%d @@@setWeatherStatus(%d)\n", __LINE__,
static_cast<int>(status));
2338 ISD::Weather::Status newStatus = status;
2343 case ISD::Weather::WEATHER_OK:
2344 statusString =
i18n(
"Weather conditions are OK.");
2347 case ISD::Weather::WEATHER_WARNING:
2348 statusString =
i18n(
"Warning: weather conditions are in the WARNING zone.");
2351 case ISD::Weather::WEATHER_ALERT:
2352 statusString =
i18n(
"Caution: weather conditions are in the DANGER zone!");
2359 qCDebug(KSTARS_EKOS_SCHEDULER) << statusString;
2361 if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_OK)
2362 weatherLabel->setPixmap(
2364 .pixmap(
QSize(32, 32)));
2365 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_WARNING)
2367 weatherLabel->setPixmap(
2369 .pixmap(
QSize(32, 32)));
2370 KSNotification::event(
QLatin1String(
"WeatherWarning"),
i18n(
"Weather conditions in warning zone"),
2371 KSNotification::Scheduler, KSNotification::Warn);
2373 else if (moduleState()->weatherStatus() == ISD::Weather::WEATHER_ALERT)
2375 weatherLabel->setPixmap(
2377 .pixmap(QSize(32, 32)));
2378 KSNotification::event(QLatin1String(
"WeatherAlert"),
2379 i18n(
"Weather conditions are critical. Observatory shutdown is imminent"), KSNotification::Scheduler,
2380 KSNotification::Alert);
2384 .pixmap(QSize(32, 32)));
2386 weatherLabel->show();
2387 weatherLabel->setToolTip(statusString);
2389 process()->appendLogText(statusString);
2391 emit weatherChanged(moduleState()->weatherStatus());
2398 weatherLabel->hide();
2401 changeSleepLabel(
i18n(
"Scheduler is in sleep mode"));
2408 case SCHEDULER_RUNNING:
2410 pi->startAnimation();
2413 startB->setToolTip(
i18n(
"Stop Scheduler"));
2414 pauseB->setEnabled(
true);
2415 pauseB->setChecked(
false);
2418 queueLoadB->setEnabled(
false);
2419 setJobManipulation(
true,
false, leadFollowerSelectionCB->currentIndex() == INDEX_LEAD);
2421 evaluateOnlyB->setEnabled(
false);
2422 startupB->setEnabled(
false);
2423 shutdownB->setEnabled(
false);
2430 emit newStatus(newState);
2435 pauseB->setCheckable(
true);
2436 pauseB->setChecked(
true);
2439void Scheduler::handleJobsUpdated(
QJsonArray jobsList)
2445 emit jobsUpdated(jobsList);
2451 return assistant->importMosaic(payload);
2454void Scheduler::startupStateChanged(StartupState state)
2456 jobStatus->setText(startupStateString(state));
2458 switch (moduleState()->startupState())
2463 case STARTUP_COMPLETE:
2465 process()->appendLogText(
i18n(
"Manual startup procedure completed successfully."));
2469 process()->appendLogText(
i18n(
"Manual startup procedure terminated due to errors."));
2477void Scheduler::shutdownStateChanged(ShutdownState state)
2479 if (state == SHUTDOWN_COMPLETE || state == SHUTDOWN_IDLE
2480 || state == SHUTDOWN_ERROR)
2483 pi->stopAnimation();
2488 if (state == SHUTDOWN_IDLE)
2489 jobStatus->setText(
i18n(
"Idle"));
2491 jobStatus->setText(shutdownStateString(state));
2493void Scheduler::ekosStateChanged(EkosState state)
2495 if (state == EKOS_IDLE)
2497 jobStatus->setText(
i18n(
"Idle"));
2498 pi->stopAnimation();
2501 jobStatus->setText(ekosStateString(state));
2503void Scheduler::indiStateChanged(INDIState state)
2505 if (state == INDI_IDLE)
2507 jobStatus->setText(
i18n(
"Idle"));
2508 pi->stopAnimation();
2511 jobStatus->setText(indiStateString(state));
2513 refreshOpticalTrain();
2516void Scheduler::indiCommunicationStatusChanged(CommunicationStatus status)
2518 if (status == Success)
2519 refreshOpticalTrain();
2521void Scheduler::parkWaitStateChanged(ParkWaitState state)
2523 jobStatus->setText(parkWaitStateString(state));
2526SchedulerJob *Scheduler::activeJob()
2528 return moduleState()->activeJob();
2531void Scheduler::loadGlobalSettings()
2536 QVariantMap settings;
2540 key = oneWidget->objectName();
2541 value = Options::self()->property(key.
toLatin1());
2542 if (value.
isValid() && oneWidget->count() > 0)
2544 oneWidget->setCurrentText(value.
toString());
2545 settings[key] = value;
2548 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2554 key = oneWidget->objectName();
2555 value = Options::self()->property(key.
toLatin1());
2558 oneWidget->setValue(value.
toDouble());
2559 settings[key] = value;
2562 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2568 key = oneWidget->objectName();
2569 value = Options::self()->property(key.
toLatin1());
2572 oneWidget->setValue(value.
toInt());
2573 settings[key] = value;
2576 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2582 key = oneWidget->objectName();
2583 value = Options::self()->property(key.
toLatin1());
2586 oneWidget->setChecked(value.
toBool());
2587 settings[key] = value;
2590 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2596 key = oneWidget->objectName();
2597 value = Options::self()->property(key.
toLatin1());
2600 oneWidget->setText(value.
toString());
2601 settings[key] = value;
2603 if (key ==
"sequenceEdit")
2605 else if (key ==
"schedulerStartupScript")
2607 else if (key ==
"schedulerShutdownScript")
2611 qCDebug(KSTARS_EKOS_SCHEDULER) <<
"Option" << key <<
"not found!";
2617 key = oneWidget->objectName();
2618 value = Options::self()->property(key.
toLatin1());
2621 oneWidget->setChecked(value.
toBool());
2622 settings[key] = value;
2629 key = oneWidget->objectName();
2630 value = Options::self()->property(key.
toLatin1());
2634 settings[key] = value;
2640 m_GlobalSettings = m_Settings = settings;
2643void Scheduler::syncSettings()
2645 QDoubleSpinBox *dsb =
nullptr;
2646 QSpinBox *sb =
nullptr;
2647 QCheckBox *cb =
nullptr;
2648 QRadioButton *rb =
nullptr;
2649 QComboBox *cbox =
nullptr;
2650 QLineEdit *lineedit =
nullptr;
2651 QDateTimeEdit *datetimeedit =
nullptr;
2655 bool removeKey =
false;
2660 value = dsb->
value();
2666 value = sb->
value();
2694 value = lineedit->
text();
2703 Options::self()->setProperty(key.
toLatin1(), value);
2706 m_Settings.remove(key);
2708 m_Settings[key] = value;
2709 m_GlobalSettings[key] = value;
2711 m_DebounceTimer.start();
2719 emit settingsUpdated(getAllSettings());
2720 Options::self()->save();
2726QVariantMap Scheduler::getAllSettings()
const
2728 QVariantMap settings;
2732 settings.insert(oneWidget->objectName(), oneWidget->currentText());
2736 settings.insert(oneWidget->objectName(), oneWidget->value());
2740 settings.insert(oneWidget->objectName(), oneWidget->value());
2744 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2750 if (!oneWidget->objectName().startsWith(
"qt_"))
2751 settings.insert(oneWidget->objectName(), oneWidget->text());
2756 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
2761 settings.insert(oneWidget->objectName(), oneWidget->dateTime().toString(
Qt::ISODate));
2770void Scheduler::setAllSettings(
const QVariantMap &settings)
2774 disconnectSettings();
2776 for (
auto &name : settings.keys())
2782 syncControl(settings, name, comboBox);
2790 syncControl(settings, name, doubleSpinBox);
2798 syncControl(settings, name, spinBox);
2806 syncControl(settings, name, checkbox);
2814 syncControl(settings, name, lineedit);
2816 if (name ==
"sequenceEdit")
2818 else if (name ==
"fitsEdit")
2820 else if (name ==
"schedulerStartupScript")
2822 else if (name ==
"schedulerShutdownScript")
2832 syncControl(settings, name, radioButton);
2839 syncControl(settings, name, datetimeedit);
2844 m_Settings = settings;
2853void Scheduler::setTargetCoords(
const dms ra,
const dms dec,
bool isJ2000)
2857 targetCoords.setRA0(ra);
2858 targetCoords.setDec0(dec);
2859 targetCoords.apparentCoord(
static_cast<long double>(J2000), KStarsData::Instance()->updateNum()->julianDay());
2863 targetCoords.setRA(ra);
2864 targetCoords.setDec(dec);
2865 SkyPoint J2000Coord(targetCoords.ra(), targetCoords.dec());
2867 targetCoords.setRA0(J2000Coord.ra());
2868 targetCoords.setDec0(J2000Coord.dec());
2871 displayTargetCoords();
2873void Scheduler::displayTargetCoords()
2876 if (targetCoords.isValid() ==
false)
2879 if (epochCB->currentText() ==
"J2000")
2881 raBox->show(targetCoords.ra0());
2882 decBox->show(targetCoords.dec0());
2886 raBox->show(targetCoords.ra());
2887 decBox->show(targetCoords.dec());
2895bool Scheduler::syncControl(
const QVariantMap &settings,
const QString &key, QWidget * widget)
2897 QSpinBox *pSB =
nullptr;
2898 QDoubleSpinBox *pDSB =
nullptr;
2899 QCheckBox *pCB =
nullptr;
2900 QComboBox *pComboBox =
nullptr;
2901 QLineEdit *pLineEdit =
nullptr;
2902 QRadioButton *pRadioButton =
nullptr;
2903 QDateTimeEdit *pDateTimeEdit =
nullptr;
2908 const int value = settings[key].toInt(&ok);
2917 const double value = settings[key].toDouble(&ok);
2926 const bool value = settings[key].toBool();
2934 const QString value = settings[key].toString();
2940 const auto value = settings[key].toString();
2946 const bool value = settings[key].toBool();
2961void Scheduler::refreshOpticalTrain()
2963 opticalTrainCombo->blockSignals(
true);
2964 opticalTrainCombo->clear();
2965 opticalTrainCombo->addItem(
"--");
2966 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
2967 opticalTrainCombo->blockSignals(
false);
2970void Scheduler::connectSettings()
2996 if (!oneWidget->objectName().startsWith(
"qt_"))
3005void Scheduler::disconnectSettings()
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
The sky coordinates of a point in the sky.
const CachingDms & dec() const
const CachingDms & ra0() const
const CachingDms & ra() const
const CachingDms & dec0() const
bool isValid() const
isValid Check if the RA and DE fall within expected range
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.
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)
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isValid() 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)
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
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)
QTextStream & dec(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