Changeset View
Changeset View
Standalone View
Standalone View
kstars/ekos/scheduler/scheduler.cpp
Show First 20 Lines • Show All 1242 Lines • ▼ Show 20 Line(s) | 1223 | { | |||
---|---|---|---|---|---|
1243 | } | 1243 | } | ||
1244 | } | 1244 | } | ||
1245 | 1245 | | |||
1246 | void Scheduler::evaluateJobs() | 1246 | void Scheduler::evaluateJobs() | ||
1247 | { | 1247 | { | ||
1248 | /* FIXME: it is possible to evaluate jobs while KStars has a time offset, so warn the user about this */ | 1248 | /* FIXME: it is possible to evaluate jobs while KStars has a time offset, so warn the user about this */ | ||
1249 | QDateTime const now = KStarsData::Instance()->lt(); | 1249 | QDateTime const now = KStarsData::Instance()->lt(); | ||
1250 | 1250 | | |||
1251 | /* Start by refreshing the number of captures already present */ | 1251 | /* Start by refreshing the number of captures already present - unneded if not remembering job progress */ | ||
1252 | if (Options::rememberJobProgress()) | ||||
1252 | updateCompletedJobsCount(); | 1253 | updateCompletedJobsCount(); | ||
1253 | 1254 | | |||
1254 | /* Update dawn and dusk astronomical times - unconditionally in case date changed */ | 1255 | /* Update dawn and dusk astronomical times - unconditionally in case date changed */ | ||
1255 | calculateDawnDusk(); | 1256 | calculateDawnDusk(); | ||
1256 | 1257 | | |||
1257 | /* First, filter out non-schedulable jobs */ | 1258 | /* First, filter out non-schedulable jobs */ | ||
1258 | /* FIXME: jobs in state JOB_ERROR should not be in the list, reorder states */ | 1259 | /* FIXME: jobs in state JOB_ERROR should not be in the list, reorder states */ | ||
1259 | QList<SchedulerJob *> sortedJobs = jobs; | 1260 | QList<SchedulerJob *> sortedJobs = jobs; | ||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Line(s) | 1301 | case SchedulerJob::JOB_BUSY: | |||
1301 | continue; | 1302 | continue; | ||
1302 | 1303 | | |||
1303 | /* Else evaluate */ | 1304 | /* Else evaluate */ | ||
1304 | case SchedulerJob::JOB_EVALUATION: | 1305 | case SchedulerJob::JOB_EVALUATION: | ||
1305 | break; | 1306 | break; | ||
1306 | } | 1307 | } | ||
1307 | 1308 | | |||
1308 | // In case of a repeating jobs, let's make sure we have more runs left to go | 1309 | // In case of a repeating jobs, let's make sure we have more runs left to go | ||
1310 | // If we don't, re-estimate imaging time for the scheduler job before concluding | ||||
1309 | if (job->getCompletionCondition() == SchedulerJob::FINISH_REPEAT) | 1311 | if (job->getCompletionCondition() == SchedulerJob::FINISH_REPEAT) | ||
1310 | { | 1312 | { | ||
1311 | if (job->getRepeatsRemaining() == 0) | 1313 | if (job->getRepeatsRemaining() == 0) | ||
1312 | { | 1314 | { | ||
1313 | appendLogText(i18n("Job '%1' has no more batches remaining.", job->getName())); | 1315 | appendLogText(i18n("Job '%1' has no more batches remaining.", job->getName())); | ||
1316 | if (Options::rememberJobProgress()) | ||||
1317 | { | ||||
1314 | job->setState(SchedulerJob::JOB_EVALUATION); | 1318 | job->setState(SchedulerJob::JOB_EVALUATION); | ||
1319 | job->setEstimatedTime(-1); | ||||
1320 | } | ||||
1321 | else | ||||
1322 | { | ||||
1323 | job->setState(SchedulerJob::JOB_COMPLETE); | ||||
1324 | job->setEstimatedTime(0); | ||||
1325 | } | ||||
1315 | } | 1326 | } | ||
1316 | } | 1327 | } | ||
1317 | 1328 | | |||
1318 | // -1 = Job is not estimated yet | 1329 | // -1 = Job is not estimated yet | ||
1319 | // -2 = Job is estimated but time is unknown | 1330 | // -2 = Job is estimated but time is unknown | ||
1320 | // > 0 Job is estimated and time is known | 1331 | // > 0 Job is estimated and time is known | ||
1321 | if (job->getEstimatedTime() == -1) | 1332 | if (job->getEstimatedTime() == -1) | ||
1322 | { | 1333 | { | ||
▲ Show 20 Lines • Show All 2887 Lines • ▼ Show 20 Line(s) | 4217 | { | |||
4210 | appendLogText(i18n("Job '%1' is aborted.", currentJob->getName())); | 4221 | appendLogText(i18n("Job '%1' is aborted.", currentJob->getName())); | ||
4211 | setCurrentJob(nullptr); | 4222 | setCurrentJob(nullptr); | ||
4212 | schedulerTimer.start(); | 4223 | schedulerTimer.start(); | ||
4213 | } | 4224 | } | ||
4214 | // Job is complete, so check completion criteria to optimize processing | 4225 | // Job is complete, so check completion criteria to optimize processing | ||
4215 | // In any case, we're done whether the job completed successfully or not. | 4226 | // In any case, we're done whether the job completed successfully or not. | ||
4216 | else if (currentJob->getCompletionCondition() == SchedulerJob::FINISH_SEQUENCE) | 4227 | else if (currentJob->getCompletionCondition() == SchedulerJob::FINISH_SEQUENCE) | ||
4217 | { | 4228 | { | ||
4218 | /* Mark the job idle as well as all its duplicates for re-evaluation */ | 4229 | /* If we remember job progress, mark the job idle as well as all its duplicates for re-evaluation */ | ||
4230 | if (Options::rememberJobProgress()) | ||||
4231 | { | ||||
4219 | foreach(SchedulerJob *a_job, jobs) | 4232 | foreach(SchedulerJob *a_job, jobs) | ||
4220 | if (a_job == currentJob || a_job->isDuplicateOf(currentJob)) | 4233 | if (a_job == currentJob || a_job->isDuplicateOf(currentJob)) | ||
4221 | a_job->setState(SchedulerJob::JOB_IDLE); | 4234 | a_job->setState(SchedulerJob::JOB_IDLE); | ||
4235 | } | ||||
4222 | 4236 | | |||
4223 | captureBatch = 0; | 4237 | captureBatch = 0; | ||
4224 | // Stop Guiding if it was used | 4238 | // Stop Guiding if it was used | ||
4225 | stopGuiding(); | 4239 | stopGuiding(); | ||
4226 | 4240 | | |||
4227 | appendLogText(i18n("Job '%1' is complete.", currentJob->getName())); | 4241 | appendLogText(i18n("Job '%1' is complete.", currentJob->getName())); | ||
4228 | setCurrentJob(nullptr); | 4242 | setCurrentJob(nullptr); | ||
4229 | schedulerTimer.start(); | 4243 | schedulerTimer.start(); | ||
▲ Show 20 Lines • Show All 422 Lines • ▼ Show 20 Line(s) | 4662 | { | |||
4652 | if (seqJob->getUploadMode() == ISD::CCD::UPLOAD_LOCAL) | 4666 | if (seqJob->getUploadMode() == ISD::CCD::UPLOAD_LOCAL) | ||
4653 | { | 4667 | { | ||
4654 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 duration cannot be estimated time since the sequence saves the files remotely.").arg(seqName); | 4668 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 duration cannot be estimated time since the sequence saves the files remotely.").arg(seqName); | ||
4655 | schedJob->setEstimatedTime(-2); | 4669 | schedJob->setEstimatedTime(-2); | ||
4656 | qDeleteAll(seqJobs); | 4670 | qDeleteAll(seqJobs); | ||
4657 | return true; | 4671 | return true; | ||
4658 | } | 4672 | } | ||
4659 | 4673 | | |||
4674 | // Note that looping jobs will have zero repeats required. | ||||
4660 | int const captures_required = seqJob->getCount()*schedJob->getRepeatsRequired(); | 4675 | int const captures_required = seqJob->getCount()*schedJob->getRepeatsRequired(); | ||
4661 | 4676 | | |||
4662 | int captures_completed = 0; | 4677 | int captures_completed = 0; | ||
4663 | if (rememberJobProgress) | 4678 | if (rememberJobProgress) | ||
4664 | { | 4679 | { | ||
4665 | /* Enumerate sequence jobs associated to this scheduler job, and assign them a completed count. | 4680 | /* Enumerate sequence jobs associated to this scheduler job, and assign them a completed count. | ||
4666 | * | 4681 | * | ||
4667 | * The objective of this block is to fill the storage map of the scheduler job with completed counts for each capture storage. | 4682 | * The objective of this block is to fill the storage map of the scheduler job with completed counts for each capture storage. | ||
Show All 34 Lines | |||||
4702 | { | 4717 | { | ||
4703 | // Enumerate seqJobs up to the current one | 4718 | // Enumerate seqJobs up to the current one | ||
4704 | if (seqJob == prevSeqJob) | 4719 | if (seqJob == prevSeqJob) | ||
4705 | break; | 4720 | break; | ||
4706 | 4721 | | |||
4707 | // If the previous sequence signature matches the current, reduce completion count to take duplicates into account | 4722 | // If the previous sequence signature matches the current, reduce completion count to take duplicates into account | ||
4708 | if (!signature.compare(prevSeqJob->getLocalDir() + prevSeqJob->getDirectoryPostfix())) | 4723 | if (!signature.compare(prevSeqJob->getLocalDir() + prevSeqJob->getDirectoryPostfix())) | ||
4709 | { | 4724 | { | ||
4725 | // Note that looping jobs will have zero repeats required. | ||||
4710 | int const previous_captures_required = prevSeqJob->getCount()*schedJob->getRepeatsRequired(); | 4726 | int const previous_captures_required = prevSeqJob->getCount()*schedJob->getRepeatsRequired(); | ||
4711 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 has a previous duplicate sequence job requiring %2 captures.").arg(seqName).arg(previous_captures_required); | 4727 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 has a previous duplicate sequence job requiring %2 captures.").arg(seqName).arg(previous_captures_required); | ||
4712 | captures_completed -= previous_captures_required; | 4728 | captures_completed -= previous_captures_required; | ||
4713 | } | 4729 | } | ||
4714 | 4730 | | |||
4715 | // Now completed count can be needlessly negative for this job, so clamp to zero | 4731 | // Now completed count can be needlessly negative for this job, so clamp to zero | ||
4716 | if (captures_completed < 0) | 4732 | if (captures_completed < 0) | ||
4717 | captures_completed = 0; | 4733 | captures_completed = 0; | ||
4718 | 4734 | | |||
4719 | // And break if no captures remain, this job has to execute | 4735 | // And break if no captures remain, this job has to execute | ||
4720 | if (captures_completed == 0) | 4736 | if (captures_completed == 0) | ||
4721 | break; | 4737 | break; | ||
4722 | } | 4738 | } | ||
4723 | 4739 | | |||
4724 | // Finally we're only interested in the number of captures required for this sequence item | 4740 | // Finally we're only interested in the number of captures required for this sequence item | ||
4725 | if (captures_required < captures_completed) | 4741 | if (0 < captures_required && captures_required < captures_completed) | ||
4726 | captures_completed = captures_required; | 4742 | captures_completed = captures_required; | ||
4727 | 4743 | | |||
4728 | 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); | 4744 | 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); | ||
4729 | 4745 | | |||
4730 | // Update the completion count for this signature in the frame map if we still have captures to take. | 4746 | // Update the completion count for this signature in the frame map if we still have captures to take. | ||
4731 | // That frame map will be transferred to the Capture module, for which the sequence is a single batch of the scheduler job. | 4747 | // That frame map will be transferred to the Capture module, for which the sequence is a single batch of the scheduler job. | ||
4732 | // For instance, consider a scheduler job repeated 3 times and using a 3xLum sequence, so we want 9xLum in the end. | 4748 | // For instance, consider a scheduler job repeated 3 times and using a 3xLum sequence, so we want 9xLum in the end. | ||
4733 | // - If no captures are already processed, the frame map contains Lum=0 | 4749 | // - If no captures are already processed, the frame map contains Lum=0 | ||
4734 | // - If 1xLum are already processed, the frame map contains Lum=0 when the batch executes, so that 3xLum may be taken. | 4750 | // - If 1xLum are already processed, the frame map contains Lum=0 when the batch executes, so that 3xLum may be taken. | ||
4735 | // - If 3xLum are already processed, the frame map contains Lum=0 when the batch executes, as we still need more than what the sequence provides. | 4751 | // - If 3xLum are already processed, the frame map contains Lum=0 when the batch executes, as we still need more than what the sequence provides. | ||
4736 | // - If 7xLum are already processed, the frame map contains Lum=1 when the batch executes, because we now only need 2xLum to finish the job. | 4752 | // - If 7xLum are already processed, the frame map contains Lum=1 when the batch executes, because we now only need 2xLum to finish the job. | ||
4737 | // Therefore we need to specify a number of existing captures only for the last batch of the scheduler job. | 4753 | // Therefore we need to specify a number of existing captures only for the last batch of the scheduler job. | ||
4738 | // In the last batch, we only need the remainder of frames to get to the required total. | 4754 | // In the last batch, we only need the remainder of frames to get to the required total. | ||
4739 | if (captures_completed < captures_required) | 4755 | if (captures_completed < captures_required) | ||
4740 | { | 4756 | { | ||
4741 | if (captures_required - captures_completed < seqJob->getCount()) | 4757 | if (captures_required - captures_completed < seqJob->getCount()) | ||
4742 | capture_map[signature] = captures_completed % seqJob->getCount(); | 4758 | capture_map[signature] = captures_completed % seqJob->getCount(); | ||
4743 | else | 4759 | else | ||
4744 | capture_map[signature] = 0; | 4760 | capture_map[signature] = 0; | ||
4745 | } | 4761 | } | ||
4746 | else capture_map[signature] = captures_required; | 4762 | else capture_map[signature] = captures_required; | ||
4747 | 4763 | | |||
4748 | // From now on, 'captures_completed' is the number of frames completed for the *current* sequence job | 4764 | // From now on, 'captures_completed' is the number of frames completed for the *current* sequence job | ||
4749 | } | 4765 | } | ||
4766 | // Else rely on the captures done during this session | ||||
4767 | else captures_completed = schedJob->getCompletedCount(); | ||||
4750 | 4768 | | |||
4751 | 4769 | | |||
4752 | // Check if we still need any light frames. Because light frames changes the flow of the observatory startup | 4770 | // Check if we still need any light frames. Because light frames changes the flow of the observatory startup | ||
4753 | // Without light frames, there is no need to do focusing, alignment, guiding...etc | 4771 | // Without light frames, there is no need to do focusing, alignment, guiding...etc | ||
4754 | // We check if the frame type is LIGHT and if either the number of captures_completed frames is less than required | 4772 | // We check if the frame type is LIGHT and if either the number of captures_completed frames is less than required | ||
4755 | // OR if the completion condition is set to LOOP so it is never complete due to looping. | 4773 | // OR if the completion condition is set to LOOP so it is never complete due to looping. | ||
4774 | // Note that looping jobs will have zero repeats required. | ||||
4756 | // FIXME: As it is implemented now, FINISH_LOOP may loop over a capture-complete, therefore inoperant, scheduler job. | 4775 | // FIXME: As it is implemented now, FINISH_LOOP may loop over a capture-complete, therefore inoperant, scheduler job. | ||
4757 | bool const areJobCapturesComplete = !(captures_completed < captures_required || schedJob->getCompletionCondition() == SchedulerJob::FINISH_LOOP); | 4776 | bool const areJobCapturesComplete = !(captures_completed < captures_required || 0 == captures_required); | ||
4758 | if (seqJob->getFrameType() == FRAME_LIGHT) | 4777 | if (seqJob->getFrameType() == FRAME_LIGHT) | ||
4759 | { | 4778 | { | ||
4760 | if(areJobCapturesComplete) | 4779 | if(areJobCapturesComplete) | ||
4761 | { | 4780 | { | ||
4762 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 completed its sequence of %2 light frames.").arg(seqName).arg(captures_required); | 4781 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 completed its sequence of %2 light frames.").arg(seqName).arg(captures_required); | ||
4763 | } | 4782 | } | ||
4764 | } | 4783 | } | ||
4765 | else | 4784 | else | ||
4766 | { | 4785 | { | ||
4767 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 captures calibration frames.").arg(seqName); | 4786 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 captures calibration frames.").arg(seqName); | ||
4768 | } | 4787 | } | ||
4769 | 4788 | | |||
4770 | totalSequenceCount += captures_required; | 4789 | totalSequenceCount += captures_required; | ||
4771 | totalCompletedCount += rememberJobProgress ? captures_completed : 0; | 4790 | totalCompletedCount += captures_completed; | ||
4772 | 4791 | | |||
4773 | /* If captures are not complete, we have imaging time left */ | 4792 | /* If captures are not complete, we have imaging time left */ | ||
4774 | if (!areJobCapturesComplete) | 4793 | if (!areJobCapturesComplete) | ||
4775 | { | 4794 | { | ||
4776 | /* if looping, consider we always have one capture left - currently this is discarded afterwards as -2 */ | 4795 | /* if looping, consider we always have one capture left */ | ||
4777 | if (schedJob->getCompletionCondition() == SchedulerJob::FINISH_LOOP) | 4796 | unsigned int const captures_to_go = 0 < captures_required ? captures_required - captures_completed : 1; | ||
4778 | totalImagingTime += fabs((seqJob->getExposure() + seqJob->getDelay()) * 1); | 4797 | totalImagingTime += fabs((seqJob->getExposure() + seqJob->getDelay()) * captures_to_go); | ||
4779 | else | | |||
4780 | totalImagingTime += fabs((seqJob->getExposure() + seqJob->getDelay()) * (captures_required - captures_completed)); | | |||
4781 | 4798 | | |||
4782 | /* If we have light frames to process, add focus/dithering delay */ | 4799 | /* If we have light frames to process, add focus/dithering delay */ | ||
4783 | if (seqJob->getFrameType() == FRAME_LIGHT) | 4800 | if (seqJob->getFrameType() == FRAME_LIGHT) | ||
4784 | { | 4801 | { | ||
4785 | // If inSequenceFocus is true | 4802 | // If inSequenceFocus is true | ||
4786 | if (hasAutoFocus) | 4803 | if (hasAutoFocus) | ||
4787 | { | 4804 | { | ||
4788 | // 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. | 4805 | // 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. | ||
4806 | // FIXME: estimating one focus per capture is probably not realistic. | ||||
4789 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 requires a focus procedure.").arg(seqName); | 4807 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 requires a focus procedure.").arg(seqName); | ||
4790 | totalImagingTime += (captures_required - captures_completed) * 30; | 4808 | totalImagingTime += captures_to_go * 30; | ||
4791 | } | 4809 | } | ||
4792 | // If we're dithering after each exposure, that's another 10-20 seconds | 4810 | // If we're dithering after each exposure, that's another 10-20 seconds | ||
4793 | if (schedJob->getStepPipeline() & SchedulerJob::USE_GUIDE && Options::ditherEnabled()) | 4811 | if (schedJob->getStepPipeline() & SchedulerJob::USE_GUIDE && Options::ditherEnabled()) | ||
4794 | { | 4812 | { | ||
4795 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 requires a dither procedure.").arg(seqName); | 4813 | qCInfo(KSTARS_EKOS_SCHEDULER) << QString("%1 requires a dither procedure.").arg(seqName); | ||
4796 | totalImagingTime += ((captures_required - captures_completed) * 15) / Options::ditherFrames(); | 4814 | totalImagingTime += (captures_to_go * 15) / Options::ditherFrames(); | ||
4797 | } | 4815 | } | ||
4798 | } | 4816 | } | ||
4799 | } | 4817 | } | ||
4800 | } | 4818 | } | ||
4801 | 4819 | | |||
4802 | schedJob->setCapturedFramesMap(capture_map); | 4820 | schedJob->setCapturedFramesMap(capture_map); | ||
4803 | schedJob->setSequenceCount(totalSequenceCount); | 4821 | schedJob->setSequenceCount(totalSequenceCount); | ||
4804 | schedJob->setCompletedCount(totalCompletedCount); | 4822 | schedJob->setCompletedCount(totalCompletedCount); | ||
▲ Show 20 Lines • Show All 1630 Lines • ▼ Show 20 Line(s) | 6448 | { | |||
6435 | findNextJob(); | 6453 | findNextJob(); | ||
6436 | } | 6454 | } | ||
6437 | } | 6455 | } | ||
6438 | else if (status == Ekos::CAPTURE_COMPLETE) | 6456 | else if (status == Ekos::CAPTURE_COMPLETE) | ||
6439 | { | 6457 | { | ||
6440 | KNotification::event(QLatin1String("EkosScheduledImagingFinished"), | 6458 | KNotification::event(QLatin1String("EkosScheduledImagingFinished"), | ||
6441 | i18n("Ekos job (%1) - Capture finished", currentJob->getName())); | 6459 | i18n("Ekos job (%1) - Capture finished", currentJob->getName())); | ||
6442 | 6460 | | |||
6443 | | ||||
6444 | //captureInterface->call(QDBus::AutoDetect, "clearSequenceQueue"); | | |||
6445 | | ||||
6446 | currentJob->setState(SchedulerJob::JOB_COMPLETE); | 6461 | currentJob->setState(SchedulerJob::JOB_COMPLETE); | ||
6447 | findNextJob(); | 6462 | findNextJob(); | ||
6448 | } | 6463 | } | ||
6449 | else | 6464 | else if (status == Ekos::CAPTURE_IMAGE_RECEIVED) | ||
6465 | { | ||||
6466 | // We received a new image, but we don't know precisely where so update the storage map and re-estimate job times. | ||||
6467 | // FIXME: rework this once capture storage is reworked | ||||
6468 | if (Options::rememberJobProgress()) | ||||
6450 | { | 6469 | { | ||
6470 | updateCompletedJobsCount(true); | ||||
6471 | | ||||
6472 | for (SchedulerJob * job: jobs) | ||||
6473 | estimateJobTime(job); | ||||
6474 | } | ||||
6475 | // Else if we don't remember the progress on jobs, increase the completed count for the current job only - no cross-checks | ||||
6476 | else currentJob->setCompletedCount(currentJob->getCompletedCount() + 1); | ||||
6477 | | ||||
6451 | captureFailureCount = 0; | 6478 | captureFailureCount = 0; | ||
6452 | /* currentJob->setCompletedCount(currentJob->getCompletedCount() + 1); */ | | |||
6453 | } | 6479 | } | ||
6454 | } | 6480 | } | ||
6455 | } | 6481 | } | ||
6456 | 6482 | | |||
6457 | void Scheduler::setFocusStatus(Ekos::FocusState status) | 6483 | void Scheduler::setFocusStatus(Ekos::FocusState status) | ||
6458 | { | 6484 | { | ||
6459 | if (state == SCHEDULER_PAUSED || currentJob == nullptr) | 6485 | if (state == SCHEDULER_PAUSED || currentJob == nullptr) | ||
6460 | return; | 6486 | return; | ||
▲ Show 20 Lines • Show All 190 Lines • Show Last 20 Lines |