diff --git a/Tests/scheduler/repeated_jobs_no_twilight.esl b/Tests/scheduler/repeated_jobs_no_twilight.esl new file mode 100644 --- /dev/null +++ b/Tests/scheduler/repeated_jobs_no_twilight.esl @@ -0,0 +1,115 @@ + + +Default + +Dubhe +10 + +11.0622 +61.7511 + +/tmp/kstars_tests/1x1s_Lum.esq + +ASAP + + +MinimumAltitude + + +Sequence + + +Track + + + +Kocab +10 + +14.8451 +74.1555 + +/tmp/kstars_tests/1x1s_Lum.esq + +ASAP + + +MinimumAltitude + + +Sequence + + +Track + + + +Dubhe +10 + +11.0622 +61.7511 + +/tmp/kstars_tests/1x1s_Lum.esq + +ASAP + + +MinimumAltitude + + +Repeat + + +Track + + + +Kocab +10 + +14.8451 +74.1555 + +/tmp/kstars_tests/1x1s_Lum.esq + +ASAP + + +MinimumAltitude + + +Repeat + + +Track + + + +Mirfak +10 + +3.40537 +49.8612 + +/tmp/kstars_tests/1x1s_Lum.esq + +ASAP + + +MinimumAltitude + + +Loop + + +Track + + + +UnparkMount + + +ParkMount + + diff --git a/kstars/ekos/scheduler/scheduler.cpp b/kstars/ekos/scheduler/scheduler.cpp --- a/kstars/ekos/scheduler/scheduler.cpp +++ b/kstars/ekos/scheduler/scheduler.cpp @@ -1248,8 +1248,9 @@ /* FIXME: it is possible to evaluate jobs while KStars has a time offset, so warn the user about this */ QDateTime const now = KStarsData::Instance()->lt(); - /* Start by refreshing the number of captures already present */ - updateCompletedJobsCount(); + /* Start by refreshing the number of captures already present - unneded if not remembering job progress */ + if (Options::rememberJobProgress()) + updateCompletedJobsCount(); /* Update dawn and dusk astronomical times - unconditionally in case date changed */ calculateDawnDusk(); @@ -1306,12 +1307,22 @@ } // In case of a repeating jobs, let's make sure we have more runs left to go + // If we don't, re-estimate imaging time for the scheduler job before concluding if (job->getCompletionCondition() == SchedulerJob::FINISH_REPEAT) { if (job->getRepeatsRemaining() == 0) { appendLogText(i18n("Job '%1' has no more batches remaining.", job->getName())); - job->setState(SchedulerJob::JOB_EVALUATION); + if (Options::rememberJobProgress()) + { + job->setState(SchedulerJob::JOB_EVALUATION); + job->setEstimatedTime(-1); + } + else + { + job->setState(SchedulerJob::JOB_COMPLETE); + job->setEstimatedTime(0); + } } } @@ -4215,10 +4226,13 @@ // In any case, we're done whether the job completed successfully or not. else if (currentJob->getCompletionCondition() == SchedulerJob::FINISH_SEQUENCE) { - /* Mark the job idle as well as all its duplicates for re-evaluation */ - foreach(SchedulerJob *a_job, jobs) - if (a_job == currentJob || a_job->isDuplicateOf(currentJob)) - a_job->setState(SchedulerJob::JOB_IDLE); + /* If we remember job progress, mark the job idle as well as all its duplicates for re-evaluation */ + if (Options::rememberJobProgress()) + { + foreach(SchedulerJob *a_job, jobs) + if (a_job == currentJob || a_job->isDuplicateOf(currentJob)) + a_job->setState(SchedulerJob::JOB_IDLE); + } captureBatch = 0; // Stop Guiding if it was used @@ -4657,6 +4671,7 @@ return true; } + // Note that looping jobs will have zero repeats required. int const captures_required = seqJob->getCount()*schedJob->getRepeatsRequired(); int captures_completed = 0; @@ -4707,6 +4722,7 @@ // If the previous sequence signature matches the current, reduce completion count to take duplicates into account if (!signature.compare(prevSeqJob->getLocalDir() + prevSeqJob->getDirectoryPostfix())) { + // Note that looping jobs will have zero repeats required. int const previous_captures_required = prevSeqJob->getCount()*schedJob->getRepeatsRequired(); qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 has a previous duplicate sequence job requiring %2 captures.").arg(seqName).arg(previous_captures_required); captures_completed -= previous_captures_required; @@ -4722,7 +4738,7 @@ } // Finally we're only interested in the number of captures required for this sequence item - if (captures_required < captures_completed) + if (0 < captures_required && captures_required < captures_completed) captures_completed = captures_required; qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 has completed %2/%3 of its required captures in output folder '%4'.").arg(seqName).arg(captures_completed).arg(captures_required).arg(signature_path); @@ -4747,14 +4763,17 @@ // From now on, 'captures_completed' is the number of frames completed for the *current* sequence job } + // Else rely on the captures done during this session + else captures_completed = schedJob->getCompletedCount(); // Check if we still need any light frames. Because light frames changes the flow of the observatory startup // Without light frames, there is no need to do focusing, alignment, guiding...etc // We check if the frame type is LIGHT and if either the number of captures_completed frames is less than required // OR if the completion condition is set to LOOP so it is never complete due to looping. + // Note that looping jobs will have zero repeats required. // FIXME: As it is implemented now, FINISH_LOOP may loop over a capture-complete, therefore inoperant, scheduler job. - bool const areJobCapturesComplete = !(captures_completed < captures_required || schedJob->getCompletionCondition() == SchedulerJob::FINISH_LOOP); + bool const areJobCapturesComplete = !(captures_completed < captures_required || 0 == captures_required); if (seqJob->getFrameType() == FRAME_LIGHT) { if(areJobCapturesComplete) @@ -4768,32 +4787,31 @@ } totalSequenceCount += captures_required; - totalCompletedCount += rememberJobProgress ? captures_completed : 0; + totalCompletedCount += captures_completed; /* If captures are not complete, we have imaging time left */ if (!areJobCapturesComplete) { - /* if looping, consider we always have one capture left - currently this is discarded afterwards as -2 */ - if (schedJob->getCompletionCondition() == SchedulerJob::FINISH_LOOP) - totalImagingTime += fabs((seqJob->getExposure() + seqJob->getDelay()) * 1); - else - totalImagingTime += fabs((seqJob->getExposure() + seqJob->getDelay()) * (captures_required - captures_completed)); + /* if looping, consider we always have one capture left */ + unsigned int const captures_to_go = 0 < captures_required ? captures_required - captures_completed : 1; + totalImagingTime += fabs((seqJob->getExposure() + seqJob->getDelay()) * captures_to_go); /* If we have light frames to process, add focus/dithering delay */ if (seqJob->getFrameType() == FRAME_LIGHT) { // If inSequenceFocus is true if (hasAutoFocus) { // Wild guess that each in sequence auto focus takes an average of 30 seconds. It can take any where from 2 seconds to 2+ minutes. + // FIXME: estimating one focus per capture is probably not realistic. qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 requires a focus procedure.").arg(seqName); - totalImagingTime += (captures_required - captures_completed) * 30; + totalImagingTime += captures_to_go * 30; } // If we're dithering after each exposure, that's another 10-20 seconds if (schedJob->getStepPipeline() & SchedulerJob::USE_GUIDE && Options::ditherEnabled()) { qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 requires a dither procedure.").arg(seqName); - totalImagingTime += ((captures_required - captures_completed) * 15) / Options::ditherFrames(); + totalImagingTime += (captures_to_go * 15) / Options::ditherFrames(); } } } @@ -6440,16 +6458,24 @@ KNotification::event(QLatin1String("EkosScheduledImagingFinished"), i18n("Ekos job (%1) - Capture finished", currentJob->getName())); - - //captureInterface->call(QDBus::AutoDetect, "clearSequenceQueue"); - currentJob->setState(SchedulerJob::JOB_COMPLETE); findNextJob(); } - else + else if (status == Ekos::CAPTURE_IMAGE_RECEIVED) { + // We received a new image, but we don't know precisely where so update the storage map and re-estimate job times. + // FIXME: rework this once capture storage is reworked + if (Options::rememberJobProgress()) + { + updateCompletedJobsCount(true); + + for (SchedulerJob * job: jobs) + estimateJobTime(job); + } + // Else if we don't remember the progress on jobs, increase the completed count for the current job only - no cross-checks + else currentJob->setCompletedCount(currentJob->getCompletedCount() + 1); + captureFailureCount = 0; - /* currentJob->setCompletedCount(currentJob->getCompletedCount() + 1); */ } } } diff --git a/kstars/ekos/scheduler/scheduler.ui b/kstars/ekos/scheduler/scheduler.ui --- a/kstars/ekos/scheduler/scheduler.ui +++ b/kstars/ekos/scheduler/scheduler.ui @@ -1164,6 +1164,9 @@ Restart job until it is executed this many times. + + 1 + 1000 diff --git a/kstars/ekos/scheduler/schedulerjob.cpp b/kstars/ekos/scheduler/schedulerjob.cpp --- a/kstars/ekos/scheduler/schedulerjob.cpp +++ b/kstars/ekos/scheduler/schedulerjob.cpp @@ -88,6 +88,29 @@ void SchedulerJob::setCompletionCondition(const CompletionCondition &value) { completionCondition = value; + + // Update repeats requirement, looping jobs have none + switch (completionCondition) + { + case FINISH_AT: + case FINISH_LOOP: + if (0 < getRepeatsRequired()) + setRepeatsRequired(0); + break; + + case FINISH_SEQUENCE: + if (1 != getRepeatsRequired()) + setRepeatsRequired(1); + break; + + case FINISH_REPEAT: + if (0 == getRepeatsRequired()) + setRepeatsRequired(1); + break; + + default: break; + } + updateJobCell(); } @@ -298,6 +321,24 @@ void SchedulerJob::setRepeatsRequired(const uint16_t &value) { repeatsRequired = value; + + // Update completion condition to be compatible + if (1 < repeatsRequired) + { + if (FINISH_REPEAT != completionCondition) + setCompletionCondition(FINISH_REPEAT); + } + else if (0 < repeatsRequired) + { + if (FINISH_SEQUENCE != completionCondition) + setCompletionCondition(FINISH_SEQUENCE); + } + else + { + if (FINISH_LOOP != completionCondition) + setCompletionCondition(FINISH_LOOP); + } + updateJobCell(); } @@ -478,7 +519,21 @@ if (captureCountCell && captureCountCell->tableWidget()) { - captureCountCell->setText(QString("%L1/%L2").arg(completedCount).arg(sequenceCount)); + switch (completionCondition) + { + case FINISH_LOOP: + case FINISH_AT: + // If looping, display the count of completed frames + captureCountCell->setText(QString("%L1").arg(completedCount)); + break; + + case FINISH_SEQUENCE: + case FINISH_REPEAT: + default: + // If repeating, display the count of completed frames to the count of requested frames + captureCountCell->setText(QString("%L1/%L2").arg(completedCount).arg(sequenceCount)); + break; + } captureCountCell->tableWidget()->resizeColumnToContents(captureCountCell->column()); }